public inbox for [email protected]help / color / mirror / Atom feed
[PATCH v5 3/3] Allow recovery via loadable modules. 14+ messages / 3 participants [nested] [flat]
* [PATCH v5 3/3] Allow recovery via loadable modules. @ 2022-12-10 03:40 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 14+ messages in thread From: Nathan Bossart @ 2022-12-10 03:40 UTC (permalink / raw) This adds the restore_library parameter to allow archive recovery via a loadable module, rather than running shell commands. --- contrib/basic_archive/Makefile | 4 +- contrib/basic_archive/basic_archive.c | 67 ++++++- contrib/basic_archive/meson.build | 7 +- contrib/basic_archive/t/001_restore.pl | 44 +++++ doc/src/sgml/archive-modules.sgml | 168 ++++++++++++++++-- doc/src/sgml/backup.sgml | 43 ++++- doc/src/sgml/basic-archive.sgml | 33 ++-- doc/src/sgml/config.sgml | 54 +++++- doc/src/sgml/high-availability.sgml | 23 ++- src/backend/access/transam/shell_restore.c | 21 ++- src/backend/access/transam/xlog.c | 13 +- src/backend/access/transam/xlogarchive.c | 70 +++++++- src/backend/access/transam/xlogrecovery.c | 26 ++- src/backend/postmaster/checkpointer.c | 26 +++ src/backend/postmaster/pgarch.c | 7 +- src/backend/postmaster/startup.c | 23 ++- src/backend/utils/misc/guc.c | 14 ++ src/backend/utils/misc/guc_tables.c | 10 ++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/access/xlog_internal.h | 1 + src/include/access/xlogarchive.h | 44 ++++- src/include/access/xlogrecovery.h | 1 + src/include/utils/guc.h | 2 + 23 files changed, 618 insertions(+), 84 deletions(-) create mode 100644 contrib/basic_archive/t/001_restore.pl diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile index 55d299d650..487dc563f3 100644 --- a/contrib/basic_archive/Makefile +++ b/contrib/basic_archive/Makefile @@ -1,7 +1,7 @@ # contrib/basic_archive/Makefile MODULES = basic_archive -PGFILEDESC = "basic_archive - basic archive module" +PGFILEDESC = "basic_archive - basic archive and recovery module" REGRESS = basic_archive REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf @@ -9,6 +9,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c # which typical installcheck users do not have (e.g. buildfarm clients). NO_INSTALLCHECK = 1 +TAP_TESTS = 1 + ifdef USE_PGXS PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c index 28cbb6cce0..8c333c8f99 100644 --- a/contrib/basic_archive/basic_archive.c +++ b/contrib/basic_archive/basic_archive.c @@ -17,6 +17,11 @@ * a file is successfully archived and then the system crashes before * a durable record of the success has been made. * + * This file also demonstrates a basic restore library implementation that + * is roughly equivalent to the following shell command: + * + * cp /path/to/archivedir/%f %p + * * Copyright (c) 2022-2023, PostgreSQL Global Development Group * * IDENTIFICATION @@ -30,6 +35,7 @@ #include <sys/time.h> #include <unistd.h> +#include "access/xlogarchive.h" #include "common/int.h" #include "miscadmin.h" #include "postmaster/pgarch.h" @@ -48,6 +54,8 @@ static bool basic_archive_file(const char *file, const char *path); static void basic_archive_file_internal(const char *file, const char *path); static bool check_archive_directory(char **newval, void **extra, GucSource source); static bool compare_files(const char *file1, const char *file2); +static bool basic_restore_file(const char *file, const char *path, + const char *lastRestartPointFileName); /* * _PG_init @@ -87,6 +95,19 @@ _PG_archive_module_init(ArchiveModuleCallbacks *cb) cb->archive_file_cb = basic_archive_file; } +/* + * _PG_recovery_module_init + * + * Returns the module's restore callback. + */ +void +_PG_recovery_module_init(RecoveryModuleCallbacks *cb) +{ + AssertVariableIsOfType(&_PG_recovery_module_init, RecoveryModuleInit); + + cb->restore_cb = basic_restore_file; +} + /* * check_archive_directory * @@ -99,8 +120,8 @@ check_archive_directory(char **newval, void **extra, GucSource source) /* * The default value is an empty string, so we have to accept that value. - * Our check_configured callback also checks for this and prevents - * archiving from proceeding if it is still empty. + * Our check_configured and restore callbacks also check for this and + * prevent archiving or recovery from proceeding if it is still empty. */ if (*newval == NULL || *newval[0] == '\0') return true; @@ -368,3 +389,45 @@ compare_files(const char *file1, const char *file2) return ret; } + +/* + * basic_restore_file + * + * Retrieves one file from the WAL archives. + */ +static bool +basic_restore_file(const char *file, const char *path, + const char *lastRestartPointFileName) +{ + char source[MAXPGPATH]; + struct stat st; + + ereport(DEBUG1, + (errmsg("restoring \"%s\" via basic_archive", file))); + + if (archive_directory == NULL || archive_directory[0] == '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"basic_archive.archive_directory\" is not set"))); + + /* + * Check whether the file exists. If not, we return false to indicate that + * there are no more files to restore. + */ + snprintf(source, MAXPGPATH, "%s/%s", archive_directory, file); + if (stat(source, &st) != 0) + { + int elevel = (errno == ENOENT) ? DEBUG1 : ERROR; + + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", source))); + return false; + } + + copy_file(source, unconstify(char *, path)); + + ereport(DEBUG1, + (errmsg("restored \"%s\" via basic_archive", file))); + return true; +} diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build index bc1380e6f6..af4580dea9 100644 --- a/contrib/basic_archive/meson.build +++ b/contrib/basic_archive/meson.build @@ -7,7 +7,7 @@ basic_archive_sources = files( if host_system == 'windows' basic_archive_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ '--NAME', 'basic_archive', - '--FILEDESC', 'basic_archive - basic archive module',]) + '--FILEDESC', 'basic_archive - basic archive and recovery module',]) endif basic_archive = shared_module('basic_archive', @@ -31,4 +31,9 @@ tests += { # which typical runningcheck users do not have (e.g. buildfarm clients). 'runningcheck': false, }, + 'tap': { + 'tests': [ + 't/001_restore.pl', + ], + }, } diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl new file mode 100644 index 0000000000..ec8767d740 --- /dev/null +++ b/contrib/basic_archive/t/001_restore.pl @@ -0,0 +1,44 @@ + +# Copyright (c) 2022, PostgreSQL Global Development Group + +use strict; +use warnings; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +# start a node +my $node = PostgreSQL::Test::Cluster->new('node'); +$node->init(has_archiving => 1, allows_streaming => 1); +my $archive_dir = $node->archive_dir; +$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os; +$node->append_conf('postgresql.conf', "archive_command = ''"); +$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'"); +$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'"); +$node->start; + +# backup the node +my $backup = 'backup'; +$node->backup($backup); + +# generate some new WAL files +$node->safe_psql('postgres', "CREATE TABLE test (a INT);"); +$node->safe_psql('postgres', "SELECT pg_switch_wal();"); +$node->safe_psql('postgres', "INSERT INTO test VALUES (1);"); + +# shut down the node (this should archive all WAL files) +$node->stop; + +# restore from the backup +my $restore = PostgreSQL::Test::Cluster->new('restore'); +$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0); +$restore->append_conf('postgresql.conf', "restore_command = ''"); +$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'"); +$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'"); +$restore->start; + +# ensure post-backup WAL was replayed +my $result = $restore->safe_psql("postgres", "SELECT count(*) FROM test;"); +is($result, "1", "check restore content"); + +done_testing(); diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml index ef02051f7f..53e657040b 100644 --- a/doc/src/sgml/archive-modules.sgml +++ b/doc/src/sgml/archive-modules.sgml @@ -1,34 +1,40 @@ <!-- doc/src/sgml/archive-modules.sgml --> <chapter id="archive-modules"> - <title>Archive Modules</title> + <title>Archive and Recovery Modules</title> <indexterm zone="archive-modules"> - <primary>Archive Modules</primary> + <primary>Archive and Recovery Modules</primary> </indexterm> <para> PostgreSQL provides infrastructure to create custom modules for continuous - archiving (see <xref linkend="continuous-archiving"/>). While archiving via - a shell command (i.e., <xref linkend="guc-archive-command"/>) is much - simpler, a custom archive module will often be considerably more robust and - performant. + archiving and recovery (see <xref linkend="continuous-archiving"/>). While + a shell command (e.g., <xref linkend="guc-archive-command"/>, + <xref linkend="guc-restore-command"/>) is much simpler, a custom module will + often be considerably more robust and performant. </para> <para> When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL will submit completed WAL files to the module, and the server will avoid recycling or removing these WAL files until the module indicates that the files - were successfully archived. It is ultimately up to the module to decide what - to do with each WAL file, but many recommendations are listed at - <xref linkend="backup-archiving-wal"/>. + were successfully archived. When a custom + <xref linkend="guc-restore-library"/> is configured, PostgreSQL will use the + module for recovery actions. It is ultimately up to the module to decide how + to accomplish each task, but some recommendations are listed at + <xref linkend="backup-archiving-wal"/> and + <xref linkend="backup-pitr-recovery"/>. </para> <para> - Archiving modules must at least consist of an initialization function (see - <xref linkend="archive-module-init"/>) and the required callbacks (see - <xref linkend="archive-module-callbacks"/>). However, archive modules are - also permitted to do much more (e.g., declare GUCs and register background - workers). + Archive and recovery modules must at least consist of an initialization + function (see <xref linkend="archive-module-init"/> and + <xref linkend="recovery-module-init"/>) and the required callbacks (see + <xref linkend="archive-module-callbacks"/> and + <xref linkend="recovery-module-callbacks"/>). However, archive and recovery + modules are also permitted to do much more (e.g., declare GUCs and register + background workers). A module may be used for both + <varname>archive_library</varname> and <varname>restore_library</varname>. </para> <para> @@ -37,7 +43,7 @@ </para> <sect1 id="archive-module-init"> - <title>Initialization Functions</title> + <title>Archive Module Initialization Functions</title> <indexterm zone="archive-module-init"> <primary>_PG_archive_module_init</primary> </indexterm> @@ -64,6 +70,12 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb); Only the <function>archive_file_cb</function> callback is required. The others are optional. </para> + + <note> + <para> + <varname>archive_library</varname> is only loaded in the archiver process. + </para> + </note> </sect1> <sect1 id="archive-module-callbacks"> @@ -129,6 +141,132 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path); <programlisting> typedef void (*ArchiveShutdownCB) (void); +</programlisting> + </para> + </sect2> + </sect1> + + <sect1 id="recovery-module-init"> + <title>Recovery Module Initialization Functions</title> + <indexterm zone="recovery-module-init"> + <primary>_PG_recovery_module_init</primary> + </indexterm> + <para> + A recovery library is loaded by dynamically loading a shared library with the + <xref linkend="guc-restore-library"/> as the library base name. The normal + library search path is used to locate the library. To provide the required + recovery module callbacks and to indicate that the library is actually a + recovery module, it needs to provide a function named + <function>_PG_recovery_module_init</function>. This function is passed a + struct that needs to be filled with the callback function pointers for + individual actions. + +<programlisting> +typedef struct RecoveryModuleCallbacks +{ + RecoveryRestoreCB restore_cb; + RecoveryArchiveCleanupCB archive_cleanup_cb; + RecoveryEndCB recovery_end_cb; + RecoveryShutdownCB shutdown_cb; +} RecoveryModuleCallbacks; +typedef void (*RecoveryModuleInit) (struct RecoveryModuleCallbacks *cb); +</programlisting> + + The <function>restore_cb</function> callback is required for archive + recovery, but it is optional for streaming replication. The others are + always optional. + </para> + + <note> + <para> + <varname>restore_library</varname> is only loaded in the startup and + checkpointer processes and in single-user mode. + </para> + </note> + </sect1> + + <sect1 id="recovery-module-callbacks"> + <title>Recovery Module Callbacks</title> + <para> + The recovery callbacks define the actual behavior of the module. The server + will call them as required to execute recovery actions. + </para> + + <sect2 id="recovery-module-restore"> + <title>Restore Callback</title> + <para> + The <function>restore_cb</function> callback is called to retrieve a single + archived segment of the WAL file series for archive recovery or streaming + replication. + +<programlisting> +typedef bool (*RecoveryRestoreCB) (const char *file, const char *path, const char *lastRestartPointFileName); +</programlisting> + + This callback must return <literal>true</literal> only if the file was + successfully retrieved. If the file is not available in the archives, the + callback must return <literal>false</literal>. + <replaceable>file</replaceable> will contain just the file name + of the WAL file to retrieve, while <replaceable>path</replaceable> contains + the destination's relative path (including the file name). + <replaceable>lastRestartPointFileName</replaceable> will contain the name + of the file containing the last valid restart point. That is the earliest + file that must be kept to allow a restore to be restartable, so this + information can be used to truncate the archive to just the minimum + required to support restarting from the current restore. + <replaceable>lastRestartPointFileName</replaceable> is typically only used + by warm-standby configurations (see <xref linkend="warm-standby"/>). Note + that if multiple standby servers are restoring from the same archive + directory, you will need to ensure that you do not delete WAL files until + they are no longer needed by any of the servers. + </para> + </sect2> + + <sect2 id="recovery-module-archive-cleanup"> + <title>Archive Cleanup Callback</title> + <para> + The <function>archive_cleanup_cb</function> callback is called at every + restart point and is intended to provide a mechanism for cleaning up old + archived WAL files that are no longer needed by the standby server. + +<programlisting> +typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName); +</programlisting> + + <replaceable>lastRestartPointFileName</replaceable> will contain the name + of the file containing the last valid restart point, like in + <link linkend="recovery-module-restore"><function>restore_cb</function></link>. + </para> + </sect2> + + <sect2 id="recovery-module-end"> + <title>Recovery End Callback</title> + <para> + The <function>recovery_end_cb</function> callback is called once at the end + of recovery and is intended to provide a mechanism for cleanup following + replication or recovery. + +<programlisting> +typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName); +</programlisting> + + <replaceable>lastRestartPointFileName</replaceable> will contain the name + of the file containing the last valid restart point, like in + <link linkend="recovery-module-restore"><function>restore_cb</function></link>. + </para> + </sect2> + + <sect2 id="recovery-module-shutdown"> + <title>Shutdown Callback</title> + <para> + The <function>shutdown_cb</function> callback is called when a process that + has loaded the recovery module exits (e.g., after an error) or the value of + <xref linkend="guc-restore-library"/> changes. If no + <function>shutdown_cb</function> is defined, no special action is taken in + these situations. + +<programlisting> +typedef void (*RecoveryShutdownCB) (void); </programlisting> </para> </sect2> diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml index 8bab521718..d6d34078fc 100644 --- a/doc/src/sgml/backup.sgml +++ b/doc/src/sgml/backup.sgml @@ -1181,9 +1181,27 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true); <para> The key part of all this is to set up a recovery configuration that describes how you want to recover and how far the recovery should - run. The one thing that you absolutely must specify is the <varname>restore_command</varname>, - which tells <productname>PostgreSQL</productname> how to retrieve archived - WAL file segments. Like the <varname>archive_command</varname>, this is + run. The one thing that you absolutely must specify is either + <varname>restore_command</varname> or a <varname>restore_library</varname> + that defines a restore callback, which tells + <productname>PostgreSQL</productname> how to retrieve archived WAL file + segments. + </para> + + <para> + Like the <varname>archive_library</varname> parameter, + <varname>restore_library</varname> is a shared library. Since such + libraries are written in <literal>C</literal>, creating your own may + require considerably more effort than writing a shell command. However, + recovery modules can be more performant than restoring via shell, and they + will have access to many useful server resources. For more information + about creating a <varname>restore_library</varname>, see + <xref linkend="archive-modules"/>. + </para> + + <para> + Like the <varname>archive_command</varname>, + <varname>restore_command</varname> is a shell command string. It can contain <literal>%f</literal>, which is replaced by the name of the desired WAL file, and <literal>%p</literal>, which is replaced by the path name to copy the WAL file to. @@ -1202,14 +1220,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p' </para> <para> - It is important that the command return nonzero exit status on failure. - The command <emphasis>will</emphasis> be called requesting files that are not - present in the archive; it must return nonzero when so asked. This is not - an error condition. An exception is that if the command was terminated by + It is important that the <varname>restore_command</varname> return nonzero + exit status on failure, or, if you are using a + <varname>restore_library</varname>, that the restore function returns + <literal>false</literal> on failure. The command or library + <emphasis>will</emphasis> be called requesting files that are not + present in the archive; it must fail when so asked. This is not + an error condition. An exception is that if the + <varname>restore_command</varname> was terminated by a signal (other than <systemitem>SIGTERM</systemitem>, which is used as part of a database server shutdown) or an error by the shell (such as command not found), then recovery will abort and the server will not start - up. + up. Likewise, if the restore function provided by the + <varname>restore_library</varname> emits an <literal>ERROR</literal> or + <literal>FATAL</literal>, recovery will abort and the server won't start. </para> <para> @@ -1233,7 +1257,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p' close as possible given the available WAL segments). Therefore, a normal recovery will end with a <quote>file not found</quote> message, the exact text of the error message depending upon your choice of - <varname>restore_command</varname>. You may also see an error message + <varname>restore_command</varname> or <varname>restore_library</varname>. + You may also see an error message at the start of recovery for a file named something like <filename>00000001.history</filename>. This is also normal and does not indicate a problem in simple recovery situations; see diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml index 60f23d2855..11fd670dbc 100644 --- a/doc/src/sgml/basic-archive.sgml +++ b/doc/src/sgml/basic-archive.sgml @@ -8,17 +8,20 @@ </indexterm> <para> - <filename>basic_archive</filename> is an example of an archive module. This - module copies completed WAL segment files to the specified directory. This - may not be especially useful, but it can serve as a starting point for - developing your own archive module. For more information about archive - modules, see <xref linkend="archive-modules"/>. + <filename>basic_archive</filename> is an example of an archive and recovery + module. This module copies completed WAL segment files to or from the + specified directory. This may not be especially useful, but it can serve as + a starting point for developing your own archive and recovery modules. For + more information about archive and recovery modules, see + see <xref linkend="archive-modules"/>. </para> <para> - In order to function, this module must be loaded via + For use as an archive module, this module must be loaded via <xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/> - must be enabled. + must be enabled. For use as a recovery module, this module must be loaded + via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see + <xref linkend="runtime-config-wal-archive-recovery"/>). </para> <sect2 id="basic-archive-configuration-parameters"> @@ -34,11 +37,12 @@ </term> <listitem> <para> - The directory where the server should copy WAL segment files. This - directory must already exist. The default is an empty string, which - effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/> - is enabled, the server will accumulate WAL segment files in the - expectation that a value will soon be provided. + The directory where the server should copy WAL segment files to or from. + This directory must already exist. The default is an empty string, + which, when used for archiving, effectively halts WAL archival, but if + <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate + WAL segment files in the expectation that a value will soon be provided. + When an empty string is used for recovery, restore will fail. </para> </listitem> </varlistentry> @@ -46,7 +50,7 @@ <para> These parameters must be set in <filename>postgresql.conf</filename>. - Typical usage might be: + Typical usage as an archive module might be: </para> <programlisting> @@ -61,7 +65,8 @@ basic_archive.archive_directory = '/path/to/archive/directory' <title>Notes</title> <para> - Server crashes may leave temporary files with the prefix + When <filename>basic_archive</filename> is used as an archive module, server + crashes may leave temporary files with the prefix <filename>archtemp</filename> in the archive directory. It is recommended to delete such files before restarting the server after a crash. It is safe to remove such files while the server is running as long as they are unrelated diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 77574e2d4e..039d3360ac 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -3773,7 +3773,8 @@ include_dir 'conf.d' recovery when the end of archived WAL is reached, but will keep trying to continue recovery by connecting to the sending server as specified by the <varname>primary_conninfo</varname> setting and/or by fetching new WAL - segments using <varname>restore_command</varname>. For this mode, the + segments using <varname>restore_command</varname> or + <varname>restore_library</varname>. For this mode, the parameters from this section and <xref linkend="runtime-config-replication-standby"/> are of interest. Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will @@ -3801,7 +3802,8 @@ include_dir 'conf.d' <listitem> <para> The local shell command to execute to retrieve an archived segment of - the WAL file series. This parameter is required for archive recovery, + the WAL file series. Either <varname>restore_command</varname> or + <xref linkend="guc-restore-library"/> is required for archive recovery, but optional for streaming replication. Any <literal>%f</literal> in the string is replaced by the name of the file to retrieve from the archive, @@ -3836,7 +3838,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows <para> This parameter can only be set in the <filename>postgresql.conf</filename> - file or on the server command line. + file or on the server command line. It is only used if + <varname>restore_library</varname> is set to an empty string. If both + <varname>restore_command</varname> and + <varname>restore_library</varname> are set, an error will be raised. + </para> + </listitem> + </varlistentry> + + <varlistentry id="guc-restore-library" xreflabel="restore_library"> + <term><varname>restore_library</varname> (<type>string</type>) + <indexterm> + <primary><varname>restore_library</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + The library to use for recovery actions, including retrieving archived + segments of the WAL file series and executing tasks at restartpoints + and at recovery end. Either <xref linkend="guc-restore-command"/> or + <varname>restore_library</varname> is required for archive recovery, + but optional for streaming replication. If this parameter is set to an + empty string (the default), restoring via shell is enabled, and + <varname>restore_command</varname>, + <varname>archive_cleanup_command</varname> and + <varname>recovery_end_command</varname> are used. If both + <varname>restore_library</varname> and any of + <varname>restore_command</varname>, + <varname>archive_cleanup_command</varname> or + <varname>recovery_end_command</varname> are set, an error will be + raised. Otherwise, the specified shared library is used for recovery. + For more information, see <xref linkend="archive-modules"/>. + </para> + + <para> + This parameter can only be set in the + <filename>postgresql.conf</filename> file or on the server command line. </para> </listitem> </varlistentry> @@ -3881,7 +3918,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows </para> <para> This parameter can only be set in the <filename>postgresql.conf</filename> - file or on the server command line. + file or on the server command line. It is only used if + <varname>restore_library</varname> is set to an empty string. If both + <varname>archive_cleanup_command</varname> and + <varname>restore_library</varname> are set, an error will be raised. </para> </listitem> </varlistentry> @@ -3910,11 +3950,13 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows </para> <para> This parameter can only be set in the <filename>postgresql.conf</filename> - file or on the server command line. + file or on the server command line. It is only used if + <varname>restore_library</varname> is set to an empty string. If both + <varname>recovery_end_command</varname> and + <varname>restore_library</varname> are set, an error will be raised. </para> </listitem> </varlistentry> - </variablelist> </sect2> diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml index f180607528..6266e2df7f 100644 --- a/doc/src/sgml/high-availability.sgml +++ b/doc/src/sgml/high-availability.sgml @@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order. <para> In standby mode, the server continuously applies WAL received from the primary server. The standby server can read WAL from a WAL archive - (see <xref linkend="guc-restore-command"/>) or directly from the primary + (see <xref linkend="guc-restore-command"/> and + <xref linkend="guc-restore-library"/>) or directly from the primary over a TCP connection (streaming replication). The standby server will also attempt to restore any WAL found in the standby cluster's <filename>pg_wal</filename> directory. That typically happens after a server @@ -638,9 +639,11 @@ protocol to make nodes agree on a serializable transactional order. <para> At startup, the standby begins by restoring all WAL available in the - archive location, calling <varname>restore_command</varname>. Once it - reaches the end of WAL available there and <varname>restore_command</varname> - fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory. + archive location, either by calling <varname>restore_command</varname> or + by executing the <varname>restore_library</varname>'s restore callback. + Once it reaches the end of WAL available there and + <varname>restore_command</varname> or the restore callback fails, it tries + to restore any WAL available in the <filename>pg_wal</filename> directory. If that fails, and streaming replication has been configured, the standby tries to connect to the primary server and start streaming WAL from the last valid record found in archive or <filename>pg_wal</filename>. If that fails @@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order. server (see <xref linkend="backup-pitr-recovery"/>). Create a file <link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm> in the standby's cluster data - directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from + directory. Set <xref linkend="guc-restore-command"/> or + <xref linkend="guc-restore-library"/> to copy files from the WAL archive. If you plan to have multiple standby servers for high availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to <literal>latest</literal> (the default), to make the standby server follow the timeline change @@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order. <note> <para> - <xref linkend="guc-restore-command"/> should return immediately + <xref linkend="guc-restore-command"/> and restore callbacks provided by + <xref linkend="guc-restore-library"/> should return immediately if the file does not exist; the server will retry the command again if necessary. </para> @@ -731,8 +736,10 @@ protocol to make nodes agree on a serializable transactional order. <para> If you're using a WAL archive, its size can be minimized using the <xref - linkend="guc-archive-cleanup-command"/> parameter to remove files that are no - longer required by the standby server. + linkend="guc-archive-cleanup-command"/> parameter or the + <xref linkend="guc-restore-library"/>'s + <function>archive_cleanup_cb</function> callback function to remove files + that are no longer required by the standby server. The <application>pg_archivecleanup</application> utility is designed specifically to be used with <varname>archive_cleanup_command</varname> in typical single-standby configurations, see <xref linkend="pgarchivecleanup"/>. diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c index 68bf6e8556..eb101b51fb 100644 --- a/src/backend/access/transam/shell_restore.c +++ b/src/backend/access/transam/shell_restore.c @@ -3,7 +3,8 @@ * shell_restore.c * * These recovery functions use a user-specified shell command (e.g., the - * restore_command GUC). + * restore_command GUC). It is used as the default, but other modules may + * define their own recovery logic. * * Copyright (c) 2022, PostgreSQL Global Development Group * @@ -23,11 +24,25 @@ #include "storage/ipc.h" #include "utils/wait_event.h" +static bool shell_restore(const char *file, const char *path, + const char *lastRestartPointFileName); +static void shell_archive_cleanup(const char *lastRestartPointFileName); +static void shell_recovery_end(const char *lastRestartPointFileName); static bool ExecuteRecoveryCommand(const char *command, const char *commandName, bool failOnSignal, bool exitOnSigterm, uint32 wait_event_info, int fail_elevel); +void +shell_restore_init(RecoveryModuleCallbacks *cb) +{ + AssertVariableIsOfType(&shell_restore_init, RecoveryModuleInit); + + cb->restore_cb = shell_restore; + cb->archive_cleanup_cb = shell_archive_cleanup; + cb->recovery_end_cb = shell_recovery_end; +} + bool shell_restore(const char *file, const char *path, const char *lastRestartPointFileName) @@ -70,7 +85,7 @@ shell_restore(const char *file, const char *path, return ret; } -void +static void shell_archive_cleanup(const char *lastRestartPointFileName) { char *cmd; @@ -83,7 +98,7 @@ shell_archive_cleanup(const char *lastRestartPointFileName) pfree(cmd); } -void +static void shell_recovery_end(const char *lastRestartPointFileName) { char *cmd; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fdce12614a..c4ad571327 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -4883,10 +4883,11 @@ static void CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog, TimeLineID newTLI) { + /* - * Execute the recovery_end_command, if any. + * Execute the recovery-end callback, if any. */ - if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0) + if (RecoveryContext.recovery_end_cb) { char lastRestartPointFname[MAXPGPATH]; XLogSegNo restartSegNo; @@ -4903,7 +4904,7 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog, XLogFileName(lastRestartPointFname, restartTli, restartSegNo, wal_segment_size); - shell_recovery_end(lastRestartPointFname); + RecoveryContext.recovery_end_cb(lastRestartPointFname); } /* @@ -7318,9 +7319,9 @@ CreateRestartPoint(int flags) timestamptz_to_str(xtime)) : 0)); /* - * Finally, execute archive_cleanup_command, if any. + * Execute the archive-cleanup callback, if any. */ - if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0) + if (RecoveryContext.archive_cleanup_cb) { char lastRestartPointFname[MAXPGPATH]; XLogSegNo restartSegNo; @@ -7337,7 +7338,7 @@ CreateRestartPoint(int flags) XLogFileName(lastRestartPointFname, restartTli, restartSegNo, wal_segment_size); - shell_archive_cleanup(lastRestartPointFname); + RecoveryContext.archive_cleanup_cb(lastRestartPointFname); } return true; diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c index b5cb060d55..fdab7dad43 100644 --- a/src/backend/access/transam/xlogarchive.c +++ b/src/backend/access/transam/xlogarchive.c @@ -22,7 +22,9 @@ #include "access/xlog.h" #include "access/xlog_internal.h" #include "access/xlogarchive.h" +#include "access/xlogrecovery.h" #include "common/archive.h" +#include "fmgr.h" #include "miscadmin.h" #include "pgstat.h" #include "postmaster/startup.h" @@ -32,6 +34,11 @@ #include "storage/ipc.h" #include "storage/lwlock.h" +/* + * Global context for recovery-related callbacks. + */ +RecoveryModuleCallbacks RecoveryContext; + /* * Attempt to retrieve the specified file from off-line archival storage. * If successful, fill "path" with its complete path (note that this will be @@ -71,7 +78,7 @@ RestoreArchivedFile(char *path, const char *xlogfname, goto not_available; /* In standby mode, restore_command might not be supplied */ - if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0) + if (RecoveryContext.restore_cb == NULL) goto not_available; /* @@ -149,14 +156,15 @@ RestoreArchivedFile(char *path, const char *xlogfname, XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size); /* - * Check signals before restore command and reset afterwards. + * Check signals before restore callback and reset afterwards. */ PreRestoreCommand(); /* * Copy xlog from archival storage to XLOGDIR */ - ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname); + ret = RecoveryContext.restore_cb(xlogfname, xlogpath, + lastRestartPointFname); PostRestoreCommand(); @@ -603,3 +611,59 @@ XLogArchiveCleanup(const char *xlog) unlink(archiveStatusPath); /* should we complain about failure? */ } + +/* + * Loads all the recovery callbacks into our global RecoveryContext. The + * caller is responsible for validating the combination of library/command + * parameters that are set (e.g., restore_command and restore_library cannot + * both be set). + */ +void +LoadRecoveryCallbacks(void) +{ + RecoveryModuleInit init; + + /* + * If the shell command is enabled, use our special initialization + * function. Otherwise, load the library and call its + * _PG_recovery_module_init(). + */ + if (restoreLibrary[0] == '\0') + init = shell_restore_init; + else + init = (RecoveryModuleInit) + load_external_function(restoreLibrary, "_PG_recovery_module_init", + false, NULL); + + if (init == NULL) + ereport(ERROR, + (errmsg("recovery modules have to define the symbol " + "_PG_recovery_module_init"))); + + memset(&RecoveryContext, 0, sizeof(RecoveryModuleCallbacks)); + (*init) (&RecoveryContext); + + /* + * If using shell commands, remove callbacks for any commands that are not + * set. + */ + if (restoreLibrary[0] == '\0') + { + if (recoveryRestoreCommand[0] == '\0') + RecoveryContext.restore_cb = NULL; + if (archiveCleanupCommand[0] == '\0') + RecoveryContext.archive_cleanup_cb = NULL; + if (recoveryEndCommand[0] == '\0') + RecoveryContext.recovery_end_cb = NULL; + } +} + +/* + * Call the shutdown callback of the loaded recovery module, if defined. + */ +void +call_recovery_module_shutdown_cb(int code, Datum arg) +{ + if (RecoveryContext.shutdown_cb) + RecoveryContext.shutdown_cb(); +} diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c index 5e65785306..db0cd4469a 100644 --- a/src/backend/access/transam/xlogrecovery.c +++ b/src/backend/access/transam/xlogrecovery.c @@ -80,6 +80,7 @@ const struct config_enum_entry recovery_target_action_options[] = { /* options formerly taken from recovery.conf for archive recovery */ char *recoveryRestoreCommand = NULL; +char *restoreLibrary = NULL; char *recoveryEndCommand = NULL; char *archiveCleanupCommand = NULL; RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET; @@ -1053,24 +1054,37 @@ validateRecoveryParameters(void) if (!ArchiveRecoveryRequested) return; + /* + * Check for invalid combinations of the command/library parameters and + * load the callbacks. + */ + CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library", + recoveryRestoreCommand, "restore_command"); + CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library", + recoveryEndCommand, "recovery_end_command"); + before_shmem_exit(call_recovery_module_shutdown_cb, 0); + LoadRecoveryCallbacks(); + /* * Check for compulsory parameters */ if (StandbyModeRequested) { if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) && - (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)) + RecoveryContext.restore_cb == NULL) ereport(WARNING, - (errmsg("specified neither primary_conninfo nor restore_command"), - errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there."))); + (errmsg("specified neither primary_conninfo nor restore_command " + "nor a restore_library that defines a restore callback"), + errhint("The database server will regularly poll the pg_wal " + "subdirectory to check for files placed there."))); } else { - if (recoveryRestoreCommand == NULL || - strcmp(recoveryRestoreCommand, "") == 0) + if (RecoveryContext.restore_cb == NULL) ereport(FATAL, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("must specify restore_command when standby mode is not enabled"))); + errmsg("must specify restore_command or a restore_library that defines " + "a restore callback when standby mode is not enabled"))); } /* diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c index de0bbbfa79..6350fd0b83 100644 --- a/src/backend/postmaster/checkpointer.c +++ b/src/backend/postmaster/checkpointer.c @@ -38,6 +38,7 @@ #include "access/xlog.h" #include "access/xlog_internal.h" +#include "access/xlogarchive.h" #include "access/xlogrecovery.h" #include "libpq/pqsignal.h" #include "miscadmin.h" @@ -222,6 +223,16 @@ CheckpointerMain(void) */ before_shmem_exit(pgstat_before_server_shutdown, 0); + /* + * Check for invalid combinations of the command/library parameters and + * load the callbacks. We do this before setting up the exception handler + * so that any problems result in a server crash shortly after startup. + */ + CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library", + archiveCleanupCommand, "archive_cleanup_command"); + before_shmem_exit(call_recovery_module_shutdown_cb, 0); + LoadRecoveryCallbacks(); + /* * Create a memory context that we will do all our work in. We do this so * that we can reset the context during error recovery and thereby avoid @@ -548,6 +559,9 @@ HandleCheckpointerInterrupts(void) if (ConfigReloadPending) { + char *prevRestoreLibrary = pstrdup(restoreLibrary); + char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand); + ConfigReloadPending = false; ProcessConfigFile(PGC_SIGHUP); @@ -563,6 +577,18 @@ HandleCheckpointerInterrupts(void) * because of SIGHUP. */ UpdateSharedMemoryConfig(); + + CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library", + archiveCleanupCommand, "archive_cleanup_command"); + if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 || + strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0) + { + call_recovery_module_shutdown_cb(0, (Datum) 0); + LoadRecoveryCallbacks(); + } + + pfree(prevRestoreLibrary); + pfree(prevArchiveCleanupCommand); } if (ShutdownRequestPending) { diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index 8ecdb9ca23..8e91f2d70f 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -831,11 +831,8 @@ LoadArchiveLibrary(void) { ArchiveModuleInit archive_init; - if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0') - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("both archive_command and archive_library set"), - errdetail("Only one of archive_command, archive_library may be set."))); + CheckMutuallyExclusiveGUCs(XLogArchiveLibrary, "archive_library", + XLogArchiveCommand, "archive_command"); memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks)); diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c index 8786186898..f9ff2b5583 100644 --- a/src/backend/postmaster/startup.c +++ b/src/backend/postmaster/startup.c @@ -20,6 +20,7 @@ #include "postgres.h" #include "access/xlog.h" +#include "access/xlogarchive.h" #include "access/xlogrecovery.h" #include "access/xlogutils.h" #include "libpq/pqsignal.h" @@ -133,13 +134,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS) * Re-read the config file. * * If one of the critical walreceiver options has changed, flag xlog.c - * to restart it. + * to restart it. Also, check for invalid combinations of the command/library + * parameters and reload the recovery callbacks if necessary. */ static void StartupRereadConfig(void) { char *conninfo = pstrdup(PrimaryConnInfo); char *slotname = pstrdup(PrimarySlotName); + char *prevRestoreLibrary = pstrdup(restoreLibrary); + char *prevRestoreCommand = pstrdup(recoveryRestoreCommand); + char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand); bool tempSlot = wal_receiver_create_temp_slot; bool conninfoChanged; bool slotnameChanged; @@ -161,6 +166,22 @@ StartupRereadConfig(void) if (conninfoChanged || slotnameChanged || tempSlotChanged) StartupRequestWalReceiverRestart(); + + CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library", + recoveryRestoreCommand, "restore_command"); + CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library", + recoveryEndCommand, "recovery_end_command"); + if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 || + strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 || + strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0) + { + call_recovery_module_shutdown_cb(0, (Datum) 0); + LoadRecoveryCallbacks(); + } + + pfree(prevRestoreLibrary); + pfree(prevRestoreCommand); + pfree(prevRecoveryEndCommand); } /* Handle various signals that might be sent to the startup process */ diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index d52069f446..7858e9a649 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -6880,3 +6880,17 @@ call_enum_check_hook(struct config_enum *conf, int *newval, void **extra, return true; } + +/* + * ERROR if both parameters are set. + */ +void +CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name, + const char *p2val, const char *p2name) +{ + if (p1val[0] != '\0' && p2val[0] != '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("both %s and %s set", p1name, p2name), + errdetail("Only one of %s, %s may be set.", p1name, p2name))); +} diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 5025e80f89..a8a516b0c6 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -3776,6 +3776,16 @@ struct config_string ConfigureNamesString[] = NULL, NULL, NULL }, + { + {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY, + gettext_noop("Sets the library that will be called for recovery actions."), + NULL + }, + &restoreLibrary, + "", + NULL, NULL, NULL + }, + { {"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY, gettext_noop("Sets the shell command that will be executed at every restart point."), diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 4cceda4162..38fb3e0823 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -269,6 +269,7 @@ # placeholders: %p = path of file to restore # %f = file name only # e.g. 'cp /mnt/server/archivedir/%f %p' +#restore_library = '' # library to use for recovery actions #archive_cleanup_command = '' # command to execute at every restartpoint #recovery_end_command = '' # command to execute at completion of recovery diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h index 59fc7bc105..756f0898b5 100644 --- a/src/include/access/xlog_internal.h +++ b/src/include/access/xlog_internal.h @@ -400,5 +400,6 @@ extern PGDLLIMPORT bool ArchiveRecoveryRequested; extern PGDLLIMPORT bool InArchiveRecovery; extern PGDLLIMPORT bool StandbyMode; extern PGDLLIMPORT char *recoveryRestoreCommand; +extern PGDLLIMPORT char *restoreLibrary; #endif /* XLOG_INTERNAL_H */ diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h index 299304703e..71c9b88165 100644 --- a/src/include/access/xlogarchive.h +++ b/src/include/access/xlogarchive.h @@ -30,9 +30,45 @@ extern bool XLogArchiveIsReady(const char *xlog); extern bool XLogArchiveIsReadyOrDone(const char *xlog); extern void XLogArchiveCleanup(const char *xlog); -extern bool shell_restore(const char *file, const char *path, - const char *lastRestartPointFileName); -extern void shell_archive_cleanup(const char *lastRestartPointFileName); -extern void shell_recovery_end(const char *lastRestartPointFileName); +/* + * Recovery module callbacks + * + * These callback functions should be defined by recovery libraries and + * returned via _PG_recovery_module_init(). For more information about the + * purpose of each callback, refer to the recovery modules documentation. + */ +typedef bool (*RecoveryRestoreCB) (const char *file, const char *path, + const char *lastRestartPointFileName); +typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName); +typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName); +typedef void (*RecoveryShutdownCB) (void); + +typedef struct RecoveryModuleCallbacks +{ + RecoveryRestoreCB restore_cb; + RecoveryArchiveCleanupCB archive_cleanup_cb; + RecoveryEndCB recovery_end_cb; + RecoveryShutdownCB shutdown_cb; +} RecoveryModuleCallbacks; + +extern RecoveryModuleCallbacks RecoveryContext; + +/* + * Type of the shared library symbol _PG_recovery_module_init that is looked up + * when loading a recovery library. + */ +typedef void (*RecoveryModuleInit) (RecoveryModuleCallbacks *cb); + +extern PGDLLEXPORT void _PG_recovery_module_init(RecoveryModuleCallbacks *cb); + +extern void LoadRecoveryCallbacks(void); +extern void call_recovery_module_shutdown_cb(int code, Datum arg); + +/* + * Since the logic for recovery via a shell command is in the core server and + * does not need to be loaded via a shared library, it has a special + * initialization function. + */ +extern void shell_restore_init(RecoveryModuleCallbacks *cb); #endif /* XLOG_ARCHIVE_H */ diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h index 47c29350f5..35d1d09374 100644 --- a/src/include/access/xlogrecovery.h +++ b/src/include/access/xlogrecovery.h @@ -55,6 +55,7 @@ extern PGDLLIMPORT int recovery_min_apply_delay; extern PGDLLIMPORT char *PrimaryConnInfo; extern PGDLLIMPORT char *PrimarySlotName; extern PGDLLIMPORT char *recoveryRestoreCommand; +extern PGDLLIMPORT char *restoreLibrary; extern PGDLLIMPORT char *recoveryEndCommand; extern PGDLLIMPORT char *archiveCleanupCommand; diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index ba89d013e6..947597247f 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -404,6 +404,8 @@ extern void *guc_malloc(int elevel, size_t size); extern pg_nodiscard void *guc_realloc(int elevel, void *old, size_t size); extern char *guc_strdup(int elevel, const char *src); extern void guc_free(void *ptr); +extern void CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name, + const char *p2val, const char *p2name); #ifdef EXEC_BACKEND extern void write_nondefault_variables(GucContext context); -- 2.25.1 --C7zPtVaVf+AK4Oqc-- ^ permalink raw reply [nested|flat] 14+ messages in thread
* Re: Sequence Access Methods, round two @ 2024-03-14 00:40 Michael Paquier <[email protected]> 0 siblings, 1 reply; 14+ messages in thread From: Michael Paquier @ 2024-03-14 00:40 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; +Cc: Tomas Vondra <[email protected]>; Peter Smith <[email protected]>; Postgres hackers <[email protected]> On Wed, Mar 13, 2024 at 07:00:37AM +0100, Peter Eisentraut wrote: > I don't understand what the overall benefit of this change is supposed to > be. In the context of this thread, this removes the dependency of sequence value lookup to heap. > If this route were to be pursued, it should be a different function name. > We shouldn't change the signature of an existing function. I'm not so sure about that. The existing pg_sequence_last_value is undocumented and only used in a system view. -- Michael Attachments: [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc) download ^ permalink raw reply [nested|flat] 14+ messages in thread
* Re: Sequence Access Methods, round two @ 2024-03-19 01:54 Michael Paquier <[email protected]> parent: Michael Paquier <[email protected]> 0 siblings, 1 reply; 14+ messages in thread From: Michael Paquier @ 2024-03-19 01:54 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; +Cc: Tomas Vondra <[email protected]>; Peter Smith <[email protected]>; Postgres hackers <[email protected]> On Thu, Mar 14, 2024 at 09:40:29AM +0900, Michael Paquier wrote: > In the context of this thread, this removes the dependency of sequence > value lookup to heap. 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~. -- Michael Attachments: [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc) download ^ permalink raw reply [nested|flat] 14+ messages in thread
* Re: Sequence Access Methods, round two @ 2024-04-19 07:00 Michael Paquier <[email protected]> parent: Michael Paquier <[email protected]> 0 siblings, 1 reply; 14+ messages in thread From: Michael Paquier @ 2024-04-19 07:00 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; +Cc: Tomas Vondra <[email protected]>; Peter Smith <[email protected]>; Postgres hackers <[email protected]> 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 Attachments: [text/x-diff] v4-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patch (5.8K, ../../[email protected]/2-v4-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patch) download | inline diff: From fa5bb6f0ff4c99a9cc7408fe0dbab62f7ad16df2 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Tue, 27 Feb 2024 09:02:57 +0900 Subject: [PATCH v4 1/8] Switch pg_sequence_last_value() to report a tuple and 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 => '{i,o,o,o,o,o,o,o}', proargnames => '{sequence_oid,start_value,minimum_value,maximum_value,increment,cycle_option,cache_size,data_type}', prosrc => 'pg_sequence_parameters' }, -{ oid => '4032', descr => 'sequence last value', +{ oid => '4032', descr => 'sequence last value data', proname => 'pg_sequence_last_value', provolatile => 'v', proparallel => 'u', - prorettype => 'int8', proargtypes => 'regclass', + prorettype => 'record', proargtypes => 'regclass', + proallargtypes => '{regclass,bool,int8}', proargmodes => '{i,o,o}', + proargnames => '{seqid,is_called,last_value}', prosrc => 'pg_sequence_last_value' }, { oid => '275', descr => 'return the next oid for a system table', diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_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 = S.seqrelid) diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.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 = PG_GETARG_OID(0); + Datum values[PG_SEQUENCE_LAST_VALUE_COLS] = {0}; + bool nulls[PG_SEQUENCE_LAST_VALUE_COLS] = {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) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); /* open and lock sequence */ init_sequence(relid, &elm, &seqrel); @@ -1795,15 +1803,14 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) seq = read_seq_tuple(seqrel, &buf, &seqtuple); is_called = seq->is_called; - result = seq->last_value; + last_value = seq->last_value; UnlockReleaseBuffer(buf); sequence_close(seqrel, NoLock); - if (is_called) - PG_RETURN_INT64(result); - else - PG_RETURN_NULL(); + values[0] = BoolGetDatum(is_called); + values[1] = Int64GetDatum(last_value); + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } 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 TableDataInfo *tdinfo) bool called; PQExpBuffer query = createPQExpBuffer(); - 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 >= 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)); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/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 -- 2.43.0 [text/x-diff] v4-0002-Remove-FormData_pg_sequence_data-from-init_params.patch (9.2K, ../../[email protected]/3-v4-0002-Remove-FormData_pg_sequence_data-from-init_params.patch) download | inline diff: From b87140f8842948352bdd97e9ff0cc6a28bbe0ff3 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> 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/sequence.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 rel, 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); 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 = makeNode(CreateStmt); @@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) /* 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); /* @@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) { case SEQ_COL_LASTVAL: coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(seqdataform.last_value); + value[i - 1] = Int64GetDatumFast(last_value); break; case SEQ_COL_LOG: coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); @@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) ObjectAddress address; Relation rel; HeapTuple seqtuple; + bool reset_state = false; + bool is_called; + int64 last_value; HeapTuple newdatatuple; /* 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 = heap_copytuple(&datatuple); newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); + last_value = newdataform->last_value; + is_called = newdataform->is_called; UnlockReleaseBuffer(buf); /* 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); /* 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 = last_value; + newdataform->is_called = is_called; + if (reset_state) + newdataform->log_cnt = 0; fill_seq_with_data(seqrel, newdatatuple); } @@ -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). Also + * 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 overridden. * * Note: we force a sequence rewrite whenever we change parameters that affect - * 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. Currently, * 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, } /* - * 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 = 0; + *reset_state = true; /* AS type */ if (as_type != NULL) @@ -1406,7 +1423,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("INCREMENT must not be zero"))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1418,7 +1435,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, { seqform->seqcycle = boolVal(is_cycled->arg); Assert(BoolIsValid(seqform->seqcycle)); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1429,7 +1446,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (max_value != NULL && max_value->arg) { seqform->seqmax = defGetInt64(max_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || max_value != NULL || reset_max_value) { @@ -1445,7 +1462,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmax = -1; /* descending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate maximum value. No need to check INT8 as seqmax is an int64 */ @@ -1461,7 +1478,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (min_value != NULL && min_value->arg) { seqform->seqmin = defGetInt64(min_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || min_value != NULL || reset_min_value) { @@ -1477,7 +1494,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmin = 1; /* ascending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* 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 != NULL) { if (restart_value->arg != NULL) - seqdataform->last_value = defGetInt64(restart_value); + *last_value = defGetInt64(restart_value); else - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; - seqdataform->log_cnt = 0; + *last_value = seqform->seqstart; + *is_called = false; + *reset_state = true; } else if (isInit) { - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; + *last_value = seqform->seqstart; + *is_called = false; } /* 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))); /* CACHE */ @@ -1563,7 +1580,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("CACHE (%lld) must be greater than zero", (long long) seqform->seqcache))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { -- 2.43.0 [text/x-diff] v4-0003-Integrate-addition-of-attributes-for-sequences-wi.patch (11.1K, ../../[email protected]/4-v4-0003-Integrate-addition-of-attributes-for-sequences-wi.patch) download | inline diff: From 506c2fb7cf6e1ae2b0f627c8e30cbde9a02297ec Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> 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/sequence.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 = NIL; + List *atcmds = 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 = NIL; for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) { ColumnDef *coldef = NULL; @@ -198,7 +200,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) coldef->is_not_null = true; null[i - 1] = false; - stmt->tableElts = lappend(stmt->tableElts, coldef); + elts = lappend(elts, coldef); } stmt->relation = seq->sequence; @@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; + /* + * Initial relation has no attributes, these are added later. + */ + stmt->tableElts = NIL; address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + tupDesc = RelationGetDescr(rel); /* now initialize the sequence's data */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.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 = 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 = 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 = 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; case T_CreateSeqStmt: + EventTriggerAlterTableStart(parsetree); address = DefineSequence(pstate, (CreateSeqStmt *) parsetree); + /* stashed internally */ + commandCollected = true; + EventTriggerAlterTableEnd(); break; case T_AlterSeqStmt: diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/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 of sequence parent_b_seq NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type ADD COLUMN (and recurse) desc column b of table parent NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint parent_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 parent_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 of 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 table parent diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out 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 of 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 of 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 of 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 of 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 <NULL> diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/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 = "ADD COLUMN"; break; + case AT_AddColumnToSequence: + strtype = "ADD COLUMN TO SEQUENCE"; + break; case AT_AddColumnToView: strtype = "ADD COLUMN TO VIEW"; break; -- 2.43.0 [text/x-diff] v4-0004-Move-code-for-local-sequences-to-own-file.patch (52.4K, ../../[email protected]/5-v4-0004-Move-code-for-local-sequences-to-own-file.patch) download | inline diff: From 113c32eef9eef84599c0b9c9909791a4814b62b3 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> 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 => 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 => 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, btree_identify, btree_xlog PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL, hash_mask, NULL) PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, 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, NULL, 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, NULL, brin_mask, NULL) PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL, NULL, NULL) diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile index cd95eec37f..dff5a60e68 100644 --- a/src/backend/access/rmgrdesc/Makefile +++ b/src/backend/access/rmgrdesc/Makefile @@ -18,13 +18,13 @@ OBJS = \ 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/rmgrdesc/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" -#include "commands/sequence.h" +#include "access/localam.h" void -seq_desc(StringInfo buf, XLogReaderState *record) +local_seq_desc(StringInfo buf, XLogReaderState *record) { char *rec = XLogRecGetData(record); uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - xl_seq_rec *xlrec = (xl_seq_rec *) rec; + xl_local_seq_rec *xlrec = (xl_local_seq_rec *) rec; - if (info == XLOG_SEQ_LOG) + if (info == XLOG_LOCAL_SEQ_LOG) appendStringInfo(buf, "rel %u/%u/%u", xlrec->locator.spcOid, xlrec->locator.dbOid, xlrec->locator.relNumber); } const char * -seq_identify(uint8 info) +local_seq_identify(uint8 info) { const char *id = NULL; switch (info & ~XLR_INFO_MASK) { - case XLOG_SEQ_LOG: - id = "LOG"; + case XLOG_LOCAL_SEQ_LOG: + id = "LOCAL_SEQ_LOG"; break; } diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/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 = 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/sequence/Makefile index 9f9d31f542..697f89905e 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = sequence.o +OBJS = local.o sequence.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/local.c b/src/backend/access/sequence/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 tuple + * + * *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 = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (local_sequence_magic *) PageGetSpecialPointer(page); + + if (sm->magic != LOCAL_SEQ_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (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 == RELPERSISTENCE_UNLOGGED) + { + SMgrRelation srel; + + srel = 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 content + */ +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 = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(local_sequence_magic)); + sm = (local_sequence_magic *) PageGetSpecialPointer(page); + sm->magic = LOCAL_SEQ_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) + { + xl_local_seq_rec xlrec; + XLogRecPtr recptr; + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec)); + XLogRegisterData((char *) tuple->t_data, tuple->t_len); + + recptr = 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 = record->EndRecPtr; + uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; + Buffer buffer; + Page page; + Page localpage; + char *item; + Size itemsz; + xl_local_seq_rec *xlrec = (xl_local_seq_rec *) XLogRecGetData(record); + local_sequence_magic *sm; + + if (info != XLOG_LOCAL_SEQ_LOG) + elog(PANIC, "seq_redo: unknown op code %u", info); + + buffer = XLogInitBufferForRedo(record, 0); + page = (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 = (Page) palloc(BufferGetPageSize(buffer)); + + PageInit(localpage, BufferGetPageSize(buffer), sizeof(local_sequence_magic)); + sm = (local_sequence_magic *) PageGetSpecialPointer(localpage); + sm->magic = LOCAL_SEQ_MAGIC; + + item = (char *) xlrec + sizeof(xl_local_seq_rec); + itemsz = XLogRecGetDataLen(record) - sizeof(xl_local_seq_rec); + + if (PageAddItem(localpage, (Item) item, itemsz, + FirstOffsetNumber, false, false) == 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 = 0; + int64 log; + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_sequence_data seq; + Page page; + bool logit = false; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + page = BufferGetPage(buf); + + *last = next = result = seq->last_value; + fetch = cache; + log = 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 = log = fetch + SEQ_LOG_VALS; + logit = true; + } + else + { + XLogRecPtr redoptr = GetRedoRecPtr(); + + if (PageGetLSN(page) <= redoptr) + { + /* last update of seq was before checkpoint */ + fetch = log = fetch + SEQ_LOG_VALS; + logit = 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 >= 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 = minv; + } + else + next += incby; + } + else + { + /* descending sequence */ + if ((minv < 0 && next < minv - incby) || + (minv >= 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 = maxv; + } + else + next += incby; + } + fetch--; + if (rescnt < cache) + { + log--; + rescnt++; + *last = next; + if (rescnt == 1) /* if it's first result - */ + result = next; /* it's what to return */ + } + } + + log -= fetch; /* adjust for any unfetched numbers */ + Assert(log >= 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 = next; + seq->is_called = true; + seq->log_cnt = 0; + + xlrec.locator = rel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG); + + PageSetLSN(page, recptr); + } + + /* Now update sequence tuple to the intended final state */ + seq->last_value = *last; /* last fetched number */ + seq->is_called = true; + seq->log_cnt = 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 = NIL; + List *atcmds = NIL; + ListCell *lc; + TupleDesc tupdesc; + HeapTuple tuple; + + /* + * Create relation (and fill value[] and null[] for the initial tuple). + */ + for (int i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) + { + ColumnDef *coldef = NULL; + + switch (i) + { + case SEQ_COL_LASTVAL: + coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatumFast(last_value); + break; + case SEQ_COL_LOG: + coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatum(0); + break; + case SEQ_COL_CALLED: + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + value[i - 1] = BoolGetDatum(is_called); + break; + } + + coldef->is_not_null = true; + null[i - 1] = false; + elts = lappend(elts, coldef); + } + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + tupdesc = RelationGetDescr(rel); + tuple = 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 = read_seq_tuple(rel, &buf, &seqdatatuple); + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->last_value = next; /* last fetched number */ + seq->is_called = iscalled; + seq->log_cnt = 0; + + MarkBufferDirty(buf); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel)) + { + xl_local_seq_rec xlrec; + XLogRecPtr recptr; + Page page = BufferGetPage(buf); + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = 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_state) +{ + 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 = 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 = (Form_pg_sequence_data) GETSTRUCT(tuple); + seq->last_value = startv; + seq->is_called = is_called; + if (reset_state) + seq->log_cnt = 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 == InvalidTransactionId); + Assert(rel->rd_rel->relminmxid == 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 = read_seq_tuple(rel, &buf, &seqdatatuple); + + *last_value = seq->last_value; + *is_called = 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/sequence/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 backend_sources += 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/sequence.c index d6f2fc3ce0..d7a6523292 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,6 +16,7 @@ #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" -/* - * 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 current * session. This is needed to hold onto nextval/currval state. (We can't @@ -96,13 +80,9 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */ */ static SeqTableData *last_used_seq = NULL; -static void fill_seq_with_data(Relation rel, HeapTuple tuple); -static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber 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_identity, 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 = NIL; - List *atcmds = NIL; - ListCell *lc; Datum pgs_values[Natts_pg_sequence]; bool pgs_nulls[Natts_pg_sequence]; - int i; /* * 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); - /* - * Create relation (and fill value[] and null[] for the tuple) - */ - for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) - { - ColumnDef *coldef = NULL; - - switch (i) - { - case SEQ_COL_LASTVAL: - coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(last_value); - break; - case SEQ_COL_LOG: - coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatum((int64) 0); - break; - case SEQ_COL_CALLED: - coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); - value[i - 1] = BoolGetDatum(false); - break; - } - - coldef->is_not_null = true; - null[i - 1] = false; - - elts = lappend(elts, coldef); - } - stmt->relation = seq->sequence; stmt->inhRelations = NIL; stmt->constraints = NIL; @@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) */ stmt->tableElts = NIL; + /* + * Initial relation has no attributes, these can be added later via the + * "init" AM callback. + */ + stmt->tableElts = NIL; + address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); - /* Add all the attributes to the sequence */ - foreach(lc, elts) - { - AlterTableCmd *atcmd; - - atcmd = makeNode(AlterTableCmd); - atcmd->subtype = AT_AddColumnToSequence; - atcmd->def = (Node *) lfirst(lc); - atcmds = lappend(atcmds, atcmd); - } - - /* - * No recursion needed. Note that EventTriggerAlterTableStart() should - * have been called. - */ - AlterTableInternal(RelationGetRelid(rel), atcmds, false); - CommandCounterIncrement(); - - tupDesc = RelationGetDescr(rel); - - /* now initialize the sequence's data */ - tuple = 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); /* 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); pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid)); if (!HeapTupleIsValid(pgstuple)) @@ -315,40 +240,8 @@ ResetSequence(Oid seq_relid) startv = pgsform->seqstart; ReleaseSysCache(pgstuple); - /* - * Copy the existing sequence tuple. - */ - tuple = 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 = (Form_pg_sequence_data) GETSTRUCT(tuple); - seq->last_value = startv; - seq->is_called = false; - seq->log_cnt = 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 == InvalidTransactionId); - Assert(seq_rel->rd_rel->relminmxid == 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); /* 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); } -/* - * 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 == RELPERSISTENCE_UNLOGGED) - { - SMgrRelation srel; - - srel = 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 content - */ -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 = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, - EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); - Assert(BufferGetBlockNumber(buf) == 0); - - page = BufferGetPage(buf); - - PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(page); - sm->magic = SEQ_MAGIC; - - /* Now insert sequence tuple */ - - /* - * Since VACUUM does not process sequences, we have to force the tuple to - * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, - InvalidOffsetNumber, false, false); - if (offnum != FirstOffsetNumber) - elog(ERROR, "failed to add sequence tuple to page"); - - /* XLOG stuff */ - if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = rel->rd_locator; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) tuple->t_data, tuple->t_len); - - recptr = 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 = false; bool is_called; int64 last_value; - HeapTuple newdatatuple; /* Open and lock sequence, and check for ownership along the way. */ relid = RangeVarGetRelidExtended(stmt->sequence, @@ -507,16 +296,8 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) seqform = (Form_pg_sequence) GETSTRUCT(seqtuple); - /* 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 = heap_copytuple(&datatuple); - newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); - last_value = newdataform->last_value; - is_called = newdataform->is_called; - - UnlockReleaseBuffer(buf); + /* Read sequence data */ + local_seq_get_state(seqrel, &last_value, &is_called); /* 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(); - /* - * 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 == InvalidTransactionId); - Assert(seqrel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - newdataform->last_value = last_value; - newdataform->is_called = is_called; - if (reset_state) - newdataform->log_cnt = 0; - fill_seq_with_data(seqrel, newdatatuple); + local_seq_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) { SeqTable elm; Relation seqrel; - Buffer buf; - HeapTupleData seqdatatuple; init_sequence(relid, &elm, &seqrel); @@ -589,10 +346,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - (void) read_seq_tuple(seqrel, &buf, &seqdatatuple); - RelationSetNewRelfilenumber(seqrel, newrelpersistence); - fill_seq_with_data(seqrel, &seqdatatuple); - UnlockReleaseBuffer(buf); + local_seq_change_persistence(seqrel, newrelpersistence); 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 = 0; + int64 result; bool cycle; - bool logit = false; /* open and lock sequence */ init_sequence(relid, &elm, &seqrel); @@ -717,105 +462,9 @@ nextval_internal(Oid relid, bool check_permissions) cycle = pgsform->seqcycle; ReleaseSysCache(pgstuple); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - page = BufferGetPage(buf); - - last = next = result = seq->last_value; - fetch = cache; - log = 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 = log = fetch + SEQ_LOG_VALS; - logit = true; - } - else - { - XLogRecPtr redoptr = GetRedoRecPtr(); - - if (PageGetLSN(page) <= redoptr) - { - /* last update of seq was before checkpoint */ - fetch = log = fetch + SEQ_LOG_VALS; - logit = 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 >= 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 = minv; - } - else - next += incby; - } - else - { - /* descending sequence */ - if ((minv < 0 && next < minv - incby) || - (minv >= 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 = maxv; - } - else - next += incby; - } - fetch--; - if (rescnt < cache) - { - log--; - rescnt++; - last = next; - if (rescnt == 1) /* if it's first result - */ - result = next; /* it's what to return */ - } - } - - log -= fetch; /* adjust for any unfetched numbers */ - Assert(log >= 0); + /* retrieve next value from the access method */ + result = local_seq_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -825,69 +474,6 @@ nextval_internal(Oid relid, bool check_permissions) last_used_seq = elm; - /* - * 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 = next; - seq->is_called = true; - seq->log_cnt = 0; - - xlrec.locator = seqrel->rd_locator; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - /* Now update sequence tuple to the intended final state */ - seq->last_value = last; /* last fetched number */ - seq->is_called = true; - seq->log_cnt = log; /* how much is logged */ - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); - sequence_close(seqrel, NoLock); 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()"); - /* lock page buffer and read tuple */ - seq = 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(); - /* ready to change the on-disk (or really, in-buffer) tuple */ - START_CRIT_SECTION(); - - seq->last_value = next; /* last fetched number */ - seq->is_called = iscalled; - seq->log_cnt = 0; - - MarkBufferDirty(buf); - - /* XLOG stuff */ - if (RelationNeedsWAL(seqrel)) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - Page page = BufferGetPage(buf); - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = seqrel->rd_locator; - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = 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); sequence_close(seqrel, NoLock); } @@ -1208,62 +759,6 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) } -/* - * Given an opened sequence relation, lock the page buffer and find the tuple - * - * *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 = ReadBuffer(rel, 0); - LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); - - page = BufferGetPage(*buf); - sm = (sequence_magic *) PageGetSpecialPointer(page); - - if (sm->magic != SEQ_MAGIC) - elog(ERROR, "bad magic number in sequence \"%s\": %08X", - RelationGetRelationName(rel), sm->magic); - - lp = PageGetItemId(page, FirstOffsetNumber); - Assert(ItemIdIsNormal(lp)); - - /* Note we currently only bother to set these two fields of *seqdatatuple */ - seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); - seqdatatuple->t_len = 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) != InvalidTransactionId) - { - HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); - seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; - seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; - MarkBufferDirtyHint(*buf, true); - } - - seq = (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; @@ -1842,12 +1334,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) errmsg("permission denied for sequence %s", RelationGetRelationName(seqrel)))); - seq = read_seq_tuple(seqrel, &buf, &seqtuple); - - is_called = seq->is_called; - last_value = seq->last_value; - - UnlockReleaseBuffer(buf); + local_seq_get_state(seqrel, &last_value, &is_called); sequence_close(seqrel, NoLock); values[0] = BoolGetDatum(is_called); @@ -1855,57 +1342,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } - -void -seq_redo(XLogReaderState *record) -{ - XLogRecPtr lsn = record->EndRecPtr; - uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - Buffer buffer; - Page page; - Page localpage; - char *item; - Size itemsz; - xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record); - sequence_magic *sm; - - if (info != XLOG_SEQ_LOG) - elog(PANIC, "seq_redo: unknown op code %u", info); - - buffer = XLogInitBufferForRedo(record, 0); - page = (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 = (Page) palloc(BufferGetPageSize(buffer)); - - PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(localpage); - sm->magic = SEQ_MAGIC; - - item = (char *) xlrec + sizeof(xl_seq_rec); - itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec); - - if (PageAddItem(localpage, (Item) item, itemsz, - FirstOffsetNumber, false, false) == 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) last_used_seq = 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" -- 2.43.0 [text/x-diff] v4-0005-Sequence-access-methods-backend-support.patch (61.2K, ../../[email protected]/6-v4-0005-Sequence-access-methods-backend-support.patch) download | inline diff: From 4363bf08870e9e1e53759ecdc1217bc31a701465 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> 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 @@ #include "access/xlogreader.h" #include "storage/relfilelocator.h" -#include "utils/rel.h" /* XLOG stuff */ #define XLOG_LOCAL_SEQ_LOG 0x00 @@ -31,18 +30,4 @@ 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/sequenceam.h b/src/include/access/sequenceam.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 value + * allocated. The result returned is the next value of the sequence computed. + */ +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 => '2', oid_symbol => 'HEAP_TABLE_AM_OID', descr => 'heap table access method', amname => 'heap', amhandler => 'heap_tableam_handler', amtype => 't' }, +{ oid => '8048', oid_symbol => 'LOCAL_SEQUENCE_AM_OID', + descr => 'local sequence access method', + amname => 'local', amhandler => 'local_sequenceam_handler', amtype => 's' }, { oid => '403', oid_symbol => 'BTREE_AM_OID', descr => 'b-tree index access method', amname => 'btree', amhandler => 'bthandler', amtype => '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 */ #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) == RELKIND_TOASTVALUE || \ (relkind) == RELKIND_MATVIEW) +/* + * Relation kinds with a sequence access method (rd_sequenceam). + */ +#define RELKIND_HAS_SEQUENCE_AM(relkind) \ + ((relkind) == RELKIND_SEQUENCE) + extern int errdetail_relkind_not_supported(char relkind); #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 => 'table_am_handler', proargtypes => 'internal', prosrc => 'heap_tableam_handler' }, +# Sequence access method handlers +{ oid => '8209', descr => 'local sequence access method handler', + proname => 'local_sequenceam_handler', provolatile => 'v', + prorettype => 'sequence_am_handler', proargtypes => 'internal', + prosrc => 'local_sequenceam_handler' }, + # Index access method handlers { oid => '330', descr => 'btree index access method handler', proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler', @@ -7627,6 +7633,13 @@ { oid => '327', descr => 'I/O', proname => 'index_am_handler_out', prorettype => 'cstring', proargtypes => 'index_am_handler', prosrc => 'index_am_handler_out' }, +{ oid => '8207', descr => 'I/O', + proname => 'sequence_am_handler_in', proisstrict => 'f', + prorettype => 'sequence_am_handler', proargtypes => 'cstring', + prosrc => 'sequence_am_handler_in' }, +{ oid => '8208', descr => 'I/O', + proname => 'sequence_am_handler_out', prorettype => 'cstring', + proargtypes => 'sequence_am_handler', prosrc => 'sequence_am_handler_out' }, { oid => '3311', descr => 'I/O', proname => 'tsm_handler_in', proisstrict => 'f', prorettype => 'tsm_handler', proargtypes => 'cstring', prosrc => '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 => 'P', typinput => 'index_am_handler_in', typoutput => 'index_am_handler_out', typreceive => '-', typsend => '-', typalign => 'i' }, +{ oid => '8210', + descr => 'pseudo-type for the result of a sequence AM handler function', + typname => 'sequence_am_handler', typlen => '4', typbyval => 't', + typtype => 'p', typcategory => 'P', typinput => 'sequence_am_handler_in', + typoutput => 'sequence_am_handler_out', typreceive => '-', typsend => '-', + typalign => 'i' }, { oid => '3310', descr => 'pseudo-type for the result of a tablesample method function', typname => 'tsm_handler', typlen => '4', typbyval => 't', typtype => '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); diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.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" -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); -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 = [ '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; 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 **extra, 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 **extra, + 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; + /* + * 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/sequence/Makefile index 697f89905e..b89a7e0526 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = local.o sequence.o +OBJS = local.o sequence.o sequenceamapi.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/local.c b/src/backend/access/sequence/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" /* @@ -297,15 +299,15 @@ local_seq_redo(XLogReaderState *record) } /* - * 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 maxv, } /* - * 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"; } /* - * 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 is_called) } /* - * 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 iscalled) } /* - * 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_state) +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_called, bool reset_state) } /* - * 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) } /* - * 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 newrelpersistence) fill_seq_with_data(rel, &seqdatatuple); UnlockReleaseBuffer(buf); } + +/* ------------------------------------------------------------------------ + * Definition of the local sequence access method. + * ------------------------------------------------------------------------ + */ +static const SequenceAmRoutine local_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = local_get_table_am, + .init = local_init, + .nextval = local_nextval, + .setval = local_setval, + .reset = local_reset, + .get_state = local_get_state, + .change_persistence = 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/sequence/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 += files( 'local.c', 'sequence.c', + 'sequenceamapi.c', ) diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/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 sequences - * (in contrast to other relation types like indexes). + * (in contrast to other relation types like indexes) that are independent + * of individual sequence access methods. * *------------------------------------------------------------------------- */ diff --git a/src/backend/access/sequence/sequenceamapi.c b/src/backend/access/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 = 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 = OidFunctionCall0(amhandler); + routine = (SequenceAmRoutine *) DatumGetPointer(datum); + + if (routine == NULL || !IsA(routine, SequenceAmRoutine)) + elog(ERROR, "sequence access method handler %u did not return a SequenceAmRoutine 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 != NULL); + Assert(routine->init != NULL); + Assert(routine->nextval != NULL); + Assert(routine->setval != NULL); + Assert(routine->reset != NULL); + Assert(routine->get_state != NULL); + Assert(routine->change_persistence != 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 = SearchSysCache1(AMOID, + ObjectIdGetDatum(amoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", amoid); + aform = (Form_pg_am) GETSTRUCT(tuple); + Assert(aform->amtype == AMTYPE_SEQUENCE); + amhandleroid = 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 == '\0') + { + GUC_check_errdetail("%s cannot be empty.", + "default_sequence_access_method"); + return false; + } + + if (strlen(*newval) >= 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 != InvalidOid) + { + if (!OidIsValid(get_sequence_am_oid(*newval, true))) + { + /* + * When source == 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 == 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 != RELKIND_TOASTVALUE) + if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != 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 @@ #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); } +/* + * 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 = INDEX_AM_HANDLEROID; break; + case AMTYPE_SEQUENCE: + expectedType = SEQUENCE_AM_HANDLEROID; + break; case AMTYPE_TABLE: expectedType = TABLE_AM_HANDLEROID; break; diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index d7a6523292..57db43501e 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,10 +16,10 @@ #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 = NIL; stmt->constraints = NIL; stmt->options = NIL; + stmt->accessMethod = seq->accessMethod ? pstrdup(seq->accessMethod) : NULL; stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; @@ -173,7 +174,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) rel = sequence_open(seqoid, AccessExclusiveLock); /* now initialize the sequence table structure and its data */ - local_seq_init(rel, last_value, is_called); + sequence_init(rel, last_value, is_called); /* process OWNED BY if given */ if (owned_by) @@ -241,7 +242,7 @@ ResetSequence(Oid seq_relid) ReleaseSysCache(pgstuple); /* Sequence state is forcibly reset here. */ - local_seq_reset(seq_rel, startv, false, true); + sequence_reset(seq_rel, startv, false, true); /* 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 = (Form_pg_sequence) GETSTRUCT(seqtuple); /* Read sequence data */ - local_seq_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); /* 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(); - local_seq_reset(seqrel, last_value, is_called, reset_state); + sequence_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -346,7 +347,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - local_seq_change_persistence(seqrel, newrelpersistence); + sequence_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -463,8 +464,8 @@ nextval_internal(Oid relid, bool check_permissions) ReleaseSysCache(pgstuple); /* retrieve next value from the access method */ - result = local_seq_nextval(seqrel, incby, maxv, minv, cache, cycle, - &last); + result = sequence_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -618,7 +619,7 @@ do_setval(Oid relid, int64 next, bool iscalled) GetTopTransactionId(); /* Call the access method callback */ - local_seq_setval(seqrel, next, iscalled); + sequence_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1334,7 +1335,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) errmsg("permission denied for sequence %s", RelationGetRelationName(seqrel)))); - local_seq_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); sequence_close(seqrel, NoLock); values[0] = BoolGetDatum(is_called); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.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 ownerId, } /* - * 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 != NULL) { - Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE); - accessMethodId = get_table_am_oid(stmt->accessMethod, false); + Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE || + RELKIND_HAS_SEQUENCE_AM(relkind)); + if (RELKIND_HAS_SEQUENCE_AM(relkind)) + accessMethodId = get_sequence_am_oid(stmt->accessMethod, false); + else + accessMethodId = get_table_am_oid(stmt->accessMethod, false); } else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE) { @@ -983,6 +988,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId)) accessMethodId = get_table_am_oid(default_table_access_method, false); } + else if (RELKIND_HAS_SEQUENCE_AM(relkind)) + { + accessMethodId = get_sequence_am_oid(default_sequence_access_method, false); + } /* * 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 = \ 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 = 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 = 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, List *aliases, Node *query); %type <str> copy_file_name access_method_clause attr_name table_access_method_clause name cursor_name file_name + sequence_access_method_clause cluster_index_specification %type <list> func_name handler_name qual_Op qual_all_Op subquery_Op @@ -4929,23 +4930,26 @@ RefreshMatViewStmt: CreateSeqStmt: CREATE OptTemp SEQUENCE qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); - $4->relpersistence = $2; n->sequence = $4; n->options = $5; + n->accessMethod = $6; n->ownerId = InvalidOid; n->if_not_exists = false; $$ = (Node *) n; } | CREATE OptTemp SEQUENCE IF_P NOT EXISTS qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); $7->relpersistence = $2; n->sequence = $7; n->options = $8; + n->accessMethod = $9; n->ownerId = InvalidOid; n->if_not_exists = true; $$ = (Node *) n; @@ -4982,6 +4986,11 @@ OptParenthesizedSeqOptList: '(' SeqOptList ')' { $$ = $2; } | /*EMPTY*/ { $$ = NIL; } ; +sequence_access_method_clause: + USING name { $$ = $2; } + | /*EMPTY*/ { $$ = NULL; } + ; + SeqOptList: SeqOptElem { $$ = list_make1($1); } | SeqOptList SeqOptElem { $$ = lappend($1, $2); } ; @@ -5978,6 +5987,7 @@ CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name am_type: INDEX { $$ = AMTYPE_INDEX; } + | SEQUENCE { $$ = AMTYPE_SEQUENCE; } | TABLE { $$ = AMTYPE_TABLE; } ; 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, ColumnDef *column, seqstmt->sequence->relpersistence = cxt->rel ? cxt->rel->rd_rel->relpersistence : cxt->relation->relpersistence; seqstmt->options = seqoptions; + seqstmt->accessMethod = NULL; /* * 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/pseudotypes.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/relcache.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 == RELKIND_INDEX || relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) RelationInitIndexAccessInfo(relation); - else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || - relation->rd_rel->relkind == RELKIND_SEQUENCE) + else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)) RelationInitTableAccessMethod(relation); else if (relation->rd_rel->relkind == 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 == InvalidOid); @@ -1811,17 +1815,9 @@ RelationInitTableAccessMethod(Relation relation) HeapTuple tuple; Form_pg_am aform; - if (relation->rd_rel->relkind == 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 == InvalidOid); - relation->rd_amhandler = 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); } +/* + * 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 != InvalidOid); + relation->rd_amhandler = GetSequenceAmRoutineId(relation->rd_rel->relam); + + /* + * Now we can fetch the sequence AM's API struct. + */ + relation->rd_sequenceam = GetSequenceAmRoutine(relation->rd_amhandler); + + /* + * From the sequence AM, set its expected table access method. + */ + tableam_name = sequence_get_table_am(relation); + tableam_oid = get_table_am_oid(tableam_name, false); + + tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(tableam_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", + tableam_oid); + aform = (Form_pg_am) GETSTRUCT(tuple); + tableam_handler = aform->amhandler; + ReleaseSysCache(tuple); + + relation->rd_tableam = GetTableAmRoutine(tableam_handler); +} + /* * formrdesc * @@ -3670,14 +3709,17 @@ RelationBuildLocalRelation(const char *relname, rel->rd_rel->relam = accessmtd; /* - * 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); - if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(relkind)) RelationInitTableAccessMethod(rel); + else if (relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); /* * Okay to insert into the relcache hash table. @@ -4292,13 +4334,21 @@ RelationCacheInitializePhase3(void) /* Reload tableam data if needed */ if (relation->rd_tableam == NULL && - (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE)) + (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))) { RelationInitTableAccessMethod(relation); Assert(relation->rd_tableam != NULL); restart = true; } + else if (relation->rd_sequenceam == NULL && + relation->rd_rel->relkind == RELKIND_SEQUENCE) + { + RelationInitSequenceAccessMethod(relation); + Assert(relation->rd_sequenceam != NULL); + + restart = true; + } /* Release hold on the relation */ RelationDecrementReferenceCount(relation); @@ -6319,8 +6369,10 @@ load_relcache_init_file(bool shared) nailed_rels++; /* Load table AM data */ - if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || rel->rd_rel->relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind)) RelationInitTableAccessMethod(rel); + else if (rel->rd_rel->relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); Assert(rel->rd_index == NULL); Assert(rel->rd_indextuple == NULL); @@ -6332,6 +6384,7 @@ load_relcache_init_file(bool shared) Assert(rel->rd_supportinfo == NULL); Assert(rel->rd_indoption == NULL); Assert(rel->rd_indcollation == NULL); + Assert(rel->rd_sequenceam == NULL); Assert(rel->rd_opcoptions == NULL); } diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_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[] = check_default_table_access_method, NULL, NULL }, + { + {"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default sequence access method for new sequences."), + 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/utils/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 = '"$user", public' # schema names #row_security = on +#default_sequence_access_method = 'local' #default_table_access_method = 'heap' #default_tablespace = '' # a tablespace name, '' uses the default #default_toast_compression = 'pglz' # 'pglz' or 'lz4' diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.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 verbose) "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")); 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 <name> 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/expected/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) PARTITION 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 = '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_dump 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 +--------- + 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/expected/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 = a1.amhandler AND a1.amtype = 't' AND -----+--------+-----+--------- (0 rows) +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + oid | amname | oid | proname +-----+--------+-----+--------- +(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 ---------+------- + Name | Type +--------+---------- 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) \dA * List of access methods - Name | Type ---------+------- + Name | Type +--------+---------- 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) \dA h* List of access methods @@ -5011,32 +5013,34 @@ List of access methods \dA: extra argument "bar" ignored \dA+ - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +--------+----------+--------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + local | Sequence | local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ * - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +--------+----------+--------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + local | Sequence | local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ h* List of access methods diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_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 FROM tableam_tbl_heap2; -- CREATE VIEW doesn't support USING CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2; --- 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 tableam_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; --- 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 = '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; -- we intentionally leave the objects created above alive, to verify pg_dump 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 = a1.amhandler AND a1.amtype = 't' AND OR p1.pronargs != 1 OR p1.proargtypes[0] != 'internal'::regtype); +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + -- **************** pg_amop **************** -- Look for illegal values in pg_amop fields diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list 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 -- 2.43.0 [text/x-diff] v4-0006-Sequence-access-methods-dump-restore-support.patch (22.2K, ../../[email protected]/7-v4-0006-Sequence-access-methods-dump-restore-support.patch) download | inline diff: From efdcdf9b0f9ee85db6de6146b55884db4128945f Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> 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_backup_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 = ropt->superuser; dopt->outputCreateDB = ropt->createDB; dopt->outputNoOwner = ropt->noOwner; + dopt->outputNoSequenceAm = ropt->noSequenceAm; dopt->outputNoTableAm = ropt->noTableAm; dopt->outputNoTablespaces = ropt->noTablespace; dopt->disable_triggers = ropt->disable_triggers; @@ -1221,6 +1222,7 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId, newToc->tag = pg_strdup(opts->tag); newToc->namespace = opts->namespace ? pg_strdup(opts->namespace) : NULL; newToc->tablespace = opts->tablespace ? pg_strdup(opts->tablespace) : NULL; + newToc->sequenceam = opts->sequenceam ? pg_strdup(opts->sequenceam) : NULL; newToc->tableam = opts->tableam ? pg_strdup(opts->tableam) : NULL; newToc->owner = opts->owner ? pg_strdup(opts->owner) : NULL; newToc->desc = pg_strdup(opts->description); @@ -2372,6 +2374,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, AH->currUser = NULL; /* unknown */ AH->currSchema = NULL; /* ditto */ + AH->currSequenceAm = NULL; /* ditto */ AH->currTablespace = NULL; /* ditto */ AH->currTableAm = NULL; /* ditto */ @@ -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 >= K_VERS_1_10) te->tablespace = ReadStr(AH); + if (AH->version >= K_VERS_1_17) + te->sequenceam = ReadStr(AH); + if (AH->version >= K_VERS_1_14) te->tableam = ReadStr(AH); @@ -3356,6 +3363,9 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname) free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; + free(AH->currTableAm); AH->currTableAm = NULL; @@ -3518,6 +3528,57 @@ _selectTablespace(ArchiveHandle *AH, const char *tablespace) destroyPQExpBuffer(qry); } +/* + * Set the proper default_sequence_access_method value for the sequence. + */ +static void +_selectSequenceAccessMethod(ArchiveHandle *AH, const char *sequenceam) +{ + RestoreOptions *ropt = AH->public.ropt; + PQExpBuffer cmd; + const char *want, + *have; + + /* do nothing in --no-sequence-access-method mode */ + if (ropt->noSequenceAm) + return; + + have = AH->currSequenceAm; + want = sequenceam; + + if (!want) + return; + + if (have && strcmp(want, have) == 0) + return; + + cmd = createPQExpBuffer(); + appendPQExpBuffer(cmd, + "SET default_sequence_access_method = %s;", + fmtId(want)); + + if (RestoringToDB(AH)) + { + PGresult *res; + + res = PQexec(AH->connection, cmd->data); + + if (!res || PQresultStatus(res) != 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 = 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); /* Emit header comment for item */ @@ -4171,6 +4233,8 @@ restore_toc_entries_prefork(ArchiveHandle *AH, TocEntry *pending_list) AH->currUser = NULL; free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; free(AH->currTablespace); AH->currTablespace = NULL; free(AH->currTableAm); @@ -4910,6 +4974,7 @@ CloneArchive(ArchiveHandle *AH) clone->connCancel = NULL; clone->currUser = NULL; clone->currSchema = NULL; + clone->currSequenceAm = NULL; clone->currTableAm = NULL; clone->currTablespace = NULL; @@ -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_backup_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 */ /* 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_VERS_REV) @@ -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 */ @@ -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 = dopt.outputSuperuser; ropt->createDB = dopt.outputCreateDB; ropt->noOwner = dopt.outputNoOwner; + ropt->noSequenceAm = dopt.outputNoSequenceAm; ropt->noTableAm = dopt.outputNoTableAm; ropt->noTablespace = dopt.outputNoTablespaces; ropt->disable_triggers = 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 assignments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); + printf(_(" --no-sequence-access-method do not sequence table access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); @@ -13306,6 +13309,9 @@ dumpAccessMethod(Archive *fout, const AccessMethodInfo *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 *tbinfo) qseqname = pg_strdup(fmtId(tbinfo->dobj.name)); - if (fout->remoteVersion >= 100000) + if (fout->remoteVersion >= 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 = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam) " + "WHERE s.seqrelid = '%u'::oid", + tbinfo->dobj.catId.oid); + } + else if (fout->remoteVersion >= 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 = '%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)); } @@ -17602,6 +17631,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) minv = PQgetvalue(res, 0, 4); cache = PQgetvalue(res, 0, 5); cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0); + seqam = PQgetvalue(res, 0, 7); /* Calculate default limits for a sequence of this type */ is_ascending = (incby[0] != '-'); @@ -17733,6 +17763,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) ARCHIVE_OPTS(.tag = tbinfo->dobj.name, .namespace = tbinfo->dobj.namespace->dobj.name, .owner = tbinfo->rolname, + .sequenceam = seqam, .description = "SEQUENCE", .section = SECTION_PRE_DATA, .createStmt = 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 = 0; static int disable_triggers = 0; static int if_exists = 0; static int inserts = 0; +static int no_sequence_access_method = 0; static int no_table_access_method = 0; static int no_tablespaces = 0; static int use_setsessauth = 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 assignments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); printf(_(" --no-sync do not wait for changes to be written safely to disk\n")); + printf(_(" --no-sequence-access-method do not dump sequence access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\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 = 0; static int if_exists = 0; static int no_data_for_failed_tables = 0; + static int outputNoSequenceAm = 0; static int outputNoTableAm = 0; static int outputNoTablespaces = 0; static int use_setsessauth = 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 = disable_triggers; opts->enable_row_security = enable_row_security; opts->noDataForFailedTables = no_data_for_failed_tables; + opts->noSequenceAm = outputNoSequenceAm; opts->noTableAm = outputNoTableAm; opts->noTablespace = outputNoTablespaces; opts->use_setsessauth = 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 access methods\n")); printf(_(" --no-table-access-method do not restore table access methods\n")); printf(_(" --no-tablespaces do not restore tablespace assignments\n")); printf(_(" --section=SECTION restore named section (pre-data, 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_dump.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 = ( 'postgres', ], }, + no_sequence_access_method => { + dump_cmd => [ + 'pg_dump', '--no-sync', + "--file=$tempdir/no_sequence_access_method.sql", + '--no-sequence-access-method', 'postgres', + ], + }, no_table_access_method => { dump_cmd => [ 'pg_dump', '--no-sync', @@ -711,6 +718,7 @@ my %full_runs = ( no_large_objects => 1, no_owner => 1, no_privs => 1, + no_sequence_access_method => 1, no_table_access_method => 1, pg_dumpall_dbprivs => 1, pg_dumpall_exclude => 1, @@ -3855,9 +3863,7 @@ my %tests = ( \QCREATE INDEX measurement_city_id_logdate_idx ON ONLY dump_test.measurement USING\E /xm, like => { - %full_runs, - %dump_test_schema_runs, - section_post_data => 1, + %full_runs, %dump_test_schema_runs, section_post_data => 1, }, unlike => { exclude_dump_test_schema => 1, @@ -4526,6 +4532,18 @@ my %tests = ( }, }, + 'CREATE ACCESS METHOD regress_test_sequence_am' => { + create_order => 11, + create_sql => + 'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER local_sequenceam_handler;', + regexp => qr/^ + \QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER local_sequenceam_handler;\E + \n/xm, + like => { + %full_runs, section_pre_data => 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 = ( }, }, + + # 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' => { + create_order => 12, + create_sql => ' + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_0 USING local; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_1 USING regress_sequence_am; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_2 USING local;', + regexp => qr/^ + \QSET default_sequence_access_method = 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 => { + %full_runs, %dump_test_schema_runs, section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + no_sequence_access_method => 1, + only_dump_measurement => 1, + }, + }, + 'CREATE MATERIALIZED VIEW regress_pg_dump_matview_am' => { create_order => 13, create_sql => ' @@ -4783,10 +4830,8 @@ $node->command_fails_like( ############################################################## # Test dumping pg_catalog (for research -- cannot be reloaded) -$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'); ######################################### # 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 </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + + <para> + This option is ignored when emitting an archive (non-text) output + file. For the archive formats, you can specify the option when you + call <command>pg_restore</command>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml 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 <replaceable class="parameter">PATTERN</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml 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 </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> -- 2.43.0 [text/x-diff] v4-0007-dummy_sequence_am-Example-of-sequence-AM.patch (10.1K, ../../[email protected]/8-v4-0007-dummy_sequence_am-Example-of-sequence-AM.patch) download | inline diff: From b7876b4616463f3d2d18eabd8eb5ab33fbb85681 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> 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.0.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.control create mode 100644 src/test/modules/dummy_sequence_am/expected/dummy_sequence.out create mode 100644 src/test/modules/dummy_sequence_am/meson.build create mode 100644 src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql 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 = \ 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/modules/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 = dummy_sequence_am + +EXTENSION = dummy_sequence_am +DATA = dummy_sequence_am--1.0.sql + +REGRESS = dummy_sequence + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/dummy_sequence_am +top_builddir = ../../../.. +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 method'; diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am.c b/src/test/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 = 1; +static bool dummy_seqam_is_called = 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 = last_value; + dummy_seqam_is_called = 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 += incby; + dummy_seqam_is_called = true; + + return dummy_seqam_last_value; +} + +static void +dummy_sequenceam_setval(Relation rel, int64 next, bool iscalled) +{ + dummy_seqam_last_value = next; + dummy_seqam_is_called = iscalled; +} + +static void +dummy_sequenceam_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + *last_value = dummy_seqam_last_value; + *is_called = dummy_seqam_is_called; +} + +static void +dummy_sequenceam_reset(Relation rel, int64 startv, bool is_called, + bool reset_state) +{ + dummy_seqam_last_value = startv; + dummy_seqam_is_called = 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 = { + .type = T_SequenceAmRoutine, + .get_table_am = dummy_sequenceam_get_table_am, + .init = dummy_sequenceam_init, + .nextval = dummy_sequenceam_nextval, + .setval = dummy_sequenceam_setval, + .get_state = dummy_sequenceam_get_state, + .reset = dummy_sequenceam_reset, + .change_persistence = 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 = 'dummy_sequence_am - sequence access method template' +default_version = '1.0' +module_pathname = '$libdir/dummy_sequence_am' +relocatable = 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 +--------- + 2 +(1 row) + +SELECT setval('dummyseq'::regclass, 14); + setval +-------- + 14 +(1 row) + +SELECT nextval('dummyseq'::regclass); + nextval +--------- + 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 +--------- + 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/modules/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 = files( + 'dummy_sequence_am.c', +) + +if host_system == 'windows' + dummy_sequence_am_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'dummy_sequence_am', + '--FILEDESC', 'dummy_sequence_am - sequence access method template',]) +endif + +dummy_sequence_am = shared_module('dummy_sequence_am', + dummy_sequence_am_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += dummy_sequence_am + +test_install_data += files( + 'dummy_sequence_am.control', + 'dummy_sequence_am--1.0.sql', +) + +tests += { + '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/src/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') -- 2.43.0 [text/x-diff] v4-0008-Sequence-access-methods-core-documentation.patch (9.6K, ../../[email protected]/9-v4-0008-Sequence-access-methods-core-documentation.patch) download | inline diff: From fc1f85a968a1f73d1b4dc7ba94b1f021e67bdc06 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> 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' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-default-sequence-access-method" xreflabel="default_sequence_access_method"> + <term><varname>default_sequence_access_method</varname> (<type>string</type>) + <indexterm> + <primary><varname>default_sequence_access_method</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + This parameter specifies the default sequence access method to use when + creating sequences if the <command>CREATE SEQUENCE</command> + command does not explicitly specify an access method. The default is + <literal>local</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-default-tablespace" xreflabel="default_tablespace"> <term><varname>default_tablespace</varname> (<type>string</type>) <indexterm> 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 @@ <!ENTITY planstats SYSTEM "planstats.sgml"> <!ENTITY tableam SYSTEM "tableam.sgml"> <!ENTITY indexam SYSTEM "indexam.sgml"> +<!ENTITY sequenceam SYSTEM "sequenceam.sgml"> <!ENTITY nls SYSTEM "nls.sgml"> <!ENTITY plhandler SYSTEM "plhandler.sgml"> <!ENTITY fdwhandler SYSTEM "fdwhandler.sgml"> 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 <replaceable class="parameter">name</replaceable> <listitem> <para> This clause specifies the type of access method to define. - Only <literal>TABLE</literal> and <literal>INDEX</literal> - are supported at present. + Only <literal>TABLE</literal>, <literal>SEQUENCE</literal> and + <literal>INDEX</literal> are supported at present. </para> </listitem> </varlistentry> @@ -77,12 +77,15 @@ CREATE ACCESS METHOD <replaceable class="parameter">name</replaceable> declared to take a single argument of type <type>internal</type>, and its return type depends on the type of access method; for <literal>TABLE</literal> access methods, it must - be <type>table_am_handler</type> and for <literal>INDEX</literal> - access methods, it must be <type>index_am_handler</type>. + be <type>table_am_handler</type>; for <literal>INDEX</literal> + access methods, it must be <type>index_am_handler</type>; + for <literal>SEQUENCE</literal>, it must be + <type>sequence_am_handler</type>; 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 <xref linkend="tableam"/> and the index access method - API is described in <xref linkend="indexam"/>. + is described in <xref linkend="tableam"/>, the index access method + API is described in <xref linkend="indexam"/> and the sequence access + method is described in <xref linkend="sequenceam"/>. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/create_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 ] <replaceab [ MINVALUE <replaceable class="parameter">minvalue</replaceable> | NO MINVALUE ] [ MAXVALUE <replaceable class="parameter">maxvalue</replaceable> | NO MAXVALUE ] [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ [ NO ] CYCLE ] [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ] + [ USING <replaceable class="parameter">access_method</replaceable> ] </synopsis> </refsynopsisdiv> @@ -261,6 +262,17 @@ SELECT * FROM <replaceable>name</replaceable>; </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>USING</literal> <replaceable class="parameter">access_method</replaceable></term> + <listitem> + <para> + The <literal>USING</literal> option specifies which sequence access + method will be used when generating the sequence numbers. The default + is <literal>local</literal>. + </para> + </listitem> + </varlistentry> </variablelist> </refsect1> 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 @@ +<!-- doc/src/sgml/sequenceam.sgml --> + +<chapter id="sequenceam"> + <title>Sequence Access Method Interface Definition</title> + + <indexterm> + <primary>Sequence Access Method</primary> + </indexterm> + <indexterm> + <primary>sequenceam</primary> + <secondary>Sequence Access Method</secondary> + </indexterm> + + <para> + This chapter explains the interface between the core + <productname>PostgreSQL</productname> system and <firstterm>sequence access + methods</firstterm>, 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. + </para> + + <para> + Each sequence access method is described by a row in the + <link linkend="catalog-pg-am"><structname>pg_am</structname></link> system + catalog. The <structname>pg_am</structname> entry specifies a name and a + <firstterm>handler function</firstterm> for the sequence access method. These + entries can be created and deleted using the + <xref linkend="sql-create-access-method"/> and + <xref linkend="sql-drop-access-method"/> SQL commands. + </para> + + <para> + A sequence access method handler function must be declared to accept a single + argument of type <type>internal</type> and to return the pseudo-type + <type>sequence_am_handler</type>. The argument is a dummy value that simply + 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 + <structname>SequenceAmRoutine</structname>, which contains everything that the + core code needs to know to make use of the sequence access method. The return + value needs to be of server lifetime, which is typically achieved by + defining it as a <literal>static const</literal> variable in global + scope. The <structname>SequenceAmRoutine</structname> struct, also called the + access method's <firstterm>API struct</firstterm>, 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 + <structname>SequenceAmRoutine</structname> structure (with comments inside + 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 <ulink url="https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/include/access/sequenceam.h;hb=HEAD"> + <filename>src/include/access/sequenceam.h</filename></ulink> file. + </para> + + <para> + Currently, the way a sequence access method stores data is fairly + unconstrained, and it is possible to use a predefined + <link linkend="tableam">Table Access Method</link> to store sequence + data. + </para> + + <para> + For crash safety, a sequence access method can use + <link linkend="wal"><acronym>WAL</acronym></link>, or a custom + implementation. + If <acronym>WAL</acronym> is chosen, either + <link linkend="generic-wal">Generic WAL Records</link> can be used, or a + <link linkend="custom-rmgr">Custom WAL Resource Manager</link> can be + implemented. + </para> + + <para> + Any developer of a new <literal>sequence access method</literal> can refer to + the existing <literal>local</literal> implementation present in + <filename>src/backend/access/sequence/local.c</filename> for details of + its implementation. + </para> +</chapter> -- 2.43.0 [application/pgp-signature] signature.asc (833B, ../../[email protected]/10-signature.asc) download ^ permalink raw reply [nested|flat] 14+ messages in thread
* Re: Sequence Access Methods, round two @ 2024-04-26 06:21 Michael Paquier <[email protected]> parent: Michael Paquier <[email protected]> 0 siblings, 1 reply; 14+ messages in thread From: Michael Paquier @ 2024-04-26 06:21 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; +Cc: Tomas Vondra <[email protected]>; Peter Smith <[email protected]>; Postgres hackers <[email protected]> On Fri, Apr 19, 2024 at 04:00:28PM +0900, Michael Paquier wrote: > 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. So, I have worked more on this patch set, and finished by reorganizing it more, with more things: - The in-core sequence access method is split into more files: -- One for its callbacks, called seqlocalam.c. -- The WAL replay routines are moved into their own file. - As asked, Implementation of a contrib module that introduces a sequence access method for snowflake IDs, to demonstrate what can be done using the APIs of the patch. The data of such sequences is stored in an unlogged table, based on the assumption that the timestamps and the machine IDs ensure the unicity of the IDs for the sequences. The advantage of what's presented here is that support for lastval(), nextval() and currval() is straight-forward. Identity columns are able to feed on that. cache is handled by sequence.c, not the AM. WAL-logging is needed for the init fork, it goes through the generic WAL APIs like bloom to log full pages. Some docs are included. This is still a rough WIP, though, and the buffer handling is not optimal, and could be made transactional this time (assuming autovacuum is able to process them at some point, or perhaps the sequence AMs should offer a way for autovacuum to know if such sequences should be cleaned up or not). After having done that, I've also found about a module developed by pgEdge, that copies a bunch of the code from sequence.c, though it is not able to handle the sequence cache: https://github.com/pgEdge/snowflake The approach this module uses is quite similar to what I have here, but it is smarter regarding clock ticking, where the internal sequence counter is bumped only when we fetch the same timestamp as a previous attempt. The module presented could be switched to do something similar by storing into the heap table used by the sequence a bit more data than just the sequence counter. Well, the point I want to make at this stage is what can be done with sequence AMs, so let's discuss about that later. Finally, custom types, where I have come up with a list of open questions: - Catalog representation. pg_sequence and pg_sequences switch to something else than int64. - The existing functions are also interesting to consider here. nextval() & co would not be usable as they are for sequence AMs that use more than int64. Note that the current design taken in the patch has a strong dependency on int64 (see sequenceam.h). So the types would need to reflect. With identity columns, the change would not be that hard as the executor has NextValueExpr. Perhaps each sequence AM should just have callback equivalents for currval(), nextval() and lastval(). This hits with the fact that this makes sequence AMs less transparent to applications because custom data types means different functions than the native ones. - Option representation. - I have polled Twitter and Fosstodon with four choices: -- No need for that, 64b representation is enough. -- Possibility to have integer-like types (MSSQL does something like that). -- Support for 128b or larger (UUIDs, etc, with text-like representation or varlenas). -- Support for binary representation, which comes down to the possibility of having sequence values even larger than 128b. Based on the first estimations, 50%-ish of people mentioned than 64b is more than enough, while Jelte mentioned that Citus has tackled this problem with an equivalent of 128b (64b for the sequence values, some more for machine states). Then there's a trend of 25%-ish in favor of 128b and 25%-ish for more than that. The results are far from being final, but that's something. My own take after pondering about it is that 64b is still more than enough for the clustering cases I've seen in the past 15 years or so, while offering room for implementations even if it comes to thousands of nodes. So there's some margin depending on the number of bits reserved for the "machine" part of the sequence IDs when used in clusters. The next plan is to hopefully be able to trigger a discussion at the next pgconf.dev at the end of May, but let's see how it goes. Thanks, -- Michael Attachments: [text/x-diff] v5-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patch (5.8K, ../../[email protected]/2-v5-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patch) download | inline diff: From 573886ba8f50ffa59024b3f1a1c31ca4a7d1e006 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Tue, 27 Feb 2024 09:02:57 +0900 Subject: [PATCH v5 1/8] Switch pg_sequence_last_value() to report a tuple and 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 => '{i,o,o,o,o,o,o,o}', proargnames => '{sequence_oid,start_value,minimum_value,maximum_value,increment,cycle_option,cache_size,data_type}', prosrc => 'pg_sequence_parameters' }, -{ oid => '4032', descr => 'sequence last value', +{ oid => '4032', descr => 'sequence last value data', proname => 'pg_sequence_last_value', provolatile => 'v', proparallel => 'u', - prorettype => 'int8', proargtypes => 'regclass', + prorettype => 'record', proargtypes => 'regclass', + proallargtypes => '{regclass,bool,int8}', proargmodes => '{i,o,o}', + proargnames => '{seqid,is_called,last_value}', prosrc => 'pg_sequence_last_value' }, { oid => '275', descr => 'return the next oid for a system table', diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_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 = S.seqrelid) diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.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 = PG_GETARG_OID(0); + Datum values[PG_SEQUENCE_LAST_VALUE_COLS] = {0}; + bool nulls[PG_SEQUENCE_LAST_VALUE_COLS] = {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) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); /* open and lock sequence */ init_sequence(relid, &elm, &seqrel); @@ -1795,15 +1803,14 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) seq = read_seq_tuple(seqrel, &buf, &seqtuple); is_called = seq->is_called; - result = seq->last_value; + last_value = seq->last_value; UnlockReleaseBuffer(buf); sequence_close(seqrel, NoLock); - if (is_called) - PG_RETURN_INT64(result); - else - PG_RETURN_NULL(); + values[0] = BoolGetDatum(is_called); + values[1] = Int64GetDatum(last_value); + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 242ebe807f..54c4f75861 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -17824,9 +17824,19 @@ dumpSequenceData(Archive *fout, const TableDataInfo *tdinfo) bool called; PQExpBuffer query = createPQExpBuffer(); - 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 >= 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)); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/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 -- 2.43.0 [text/x-diff] v5-0002-Remove-FormData_pg_sequence_data-from-init_params.patch (9.2K, ../../[email protected]/3-v5-0002-Remove-FormData_pg_sequence_data-from-init_params.patch) download | inline diff: From 6ba98cb686a66d38cec4e5dbe42969d469c180fd Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:00:45 +0900 Subject: [PATCH v5 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/sequence.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 rel, 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); 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 = makeNode(CreateStmt); @@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) /* 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); /* @@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) { case SEQ_COL_LASTVAL: coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(seqdataform.last_value); + value[i - 1] = Int64GetDatumFast(last_value); break; case SEQ_COL_LOG: coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); @@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) ObjectAddress address; Relation rel; HeapTuple seqtuple; + bool reset_state = false; + bool is_called; + int64 last_value; HeapTuple newdatatuple; /* 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 = heap_copytuple(&datatuple); newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); + last_value = newdataform->last_value; + is_called = newdataform->is_called; UnlockReleaseBuffer(buf); /* 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); /* 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 = last_value; + newdataform->is_called = is_called; + if (reset_state) + newdataform->log_cnt = 0; fill_seq_with_data(seqrel, newdatatuple); } @@ -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). Also + * 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 overridden. * * Note: we force a sequence rewrite whenever we change parameters that affect - * 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. Currently, * 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, } /* - * 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 = 0; + *reset_state = true; /* AS type */ if (as_type != NULL) @@ -1406,7 +1423,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("INCREMENT must not be zero"))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1418,7 +1435,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, { seqform->seqcycle = boolVal(is_cycled->arg); Assert(BoolIsValid(seqform->seqcycle)); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1429,7 +1446,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (max_value != NULL && max_value->arg) { seqform->seqmax = defGetInt64(max_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || max_value != NULL || reset_max_value) { @@ -1445,7 +1462,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmax = -1; /* descending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate maximum value. No need to check INT8 as seqmax is an int64 */ @@ -1461,7 +1478,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (min_value != NULL && min_value->arg) { seqform->seqmin = defGetInt64(min_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || min_value != NULL || reset_min_value) { @@ -1477,7 +1494,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmin = 1; /* ascending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* 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 != NULL) { if (restart_value->arg != NULL) - seqdataform->last_value = defGetInt64(restart_value); + *last_value = defGetInt64(restart_value); else - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; - seqdataform->log_cnt = 0; + *last_value = seqform->seqstart; + *is_called = false; + *reset_state = true; } else if (isInit) { - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; + *last_value = seqform->seqstart; + *is_called = false; } /* 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))); /* CACHE */ @@ -1563,7 +1580,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("CACHE (%lld) must be greater than zero", (long long) seqform->seqcache))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { -- 2.43.0 [text/x-diff] v5-0003-Integrate-addition-of-attributes-for-sequences-wi.patch (11.1K, ../../[email protected]/4-v5-0003-Integrate-addition-of-attributes-for-sequences-wi.patch) download | inline diff: From cf4a668d7da0906eedc634ec9882a6061795f2ef Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 19 Jan 2024 15:00:14 +0900 Subject: [PATCH v5 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 af80a5d38e..fdcebc268e 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/sequence.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 = NIL; + List *atcmds = 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 = NIL; for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) { ColumnDef *coldef = NULL; @@ -198,7 +200,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) coldef->is_not_null = true; null[i - 1] = false; - stmt->tableElts = lappend(stmt->tableElts, coldef); + elts = lappend(elts, coldef); } stmt->relation = seq->sequence; @@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; + /* + * Initial relation has no attributes, these are added later. + */ + stmt->tableElts = NIL; address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + tupDesc = RelationGetDescr(rel); /* now initialize the sequence's data */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 3556240c8e..ad24777bb8 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4555,6 +4555,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 */ @@ -4858,6 +4859,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* Recursion occurs during execution phase */ pass = 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 = 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, @@ -5285,6 +5293,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 = ATExecAddColumn(wqueue, tab, rel, &cmd, cmd->recurse, false, @@ -6465,6 +6474,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; case T_CreateSeqStmt: + EventTriggerAlterTableStart(parsetree); address = DefineSequence(pstate, (CreateSeqStmt *) parsetree); + /* stashed internally */ + commandCollected = true; + EventTriggerAlterTableEnd(); break; case T_AlterSeqStmt: diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/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 of sequence parent_b_seq NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type ADD COLUMN (and recurse) desc column b of table parent NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint parent_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 parent_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 of 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 table parent diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out 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 of 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 of 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 of 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 of 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 <NULL> diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/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 = "ADD COLUMN"; break; + case AT_AddColumnToSequence: + strtype = "ADD COLUMN TO SEQUENCE"; + break; case AT_AddColumnToView: strtype = "ADD COLUMN TO VIEW"; break; -- 2.43.0 [text/x-diff] v5-0004-Refactor-code-for-in-core-local-sequences.patch (54.1K, ../../[email protected]/5-v5-0004-Refactor-code-for-in-core-local-sequences.patch) download | inline diff: From 060ec6d4fa0e8165c72fea39ca5c7b63cc8e4d91 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Thu, 25 Apr 2024 16:49:47 +0900 Subject: [PATCH v5 4/8] Refactor code for in-core "local" sequences This commit restructures the code of in-core sequences into a new set of files: - seqdesc.c is renamed to seqlocaldesc.c. - seqlocalxlog.c to the code in the WAL replay logic. - seqlocalam.c to store a set of routines called from sequence.c, finishing the separation between the main sequence logic and the in-core sequences. - seqlocalam.h to store the AM-specific structures and routine related to the in-core sequences. WAL records are renamed to "SequenceLocal" with structures, variables and file structures mapping to that. --- src/include/access/rmgrlist.h | 2 +- src/include/access/seqlocalam.h | 58 ++ src/backend/access/rmgrdesc/Makefile | 2 +- src/backend/access/rmgrdesc/meson.build | 2 +- .../rmgrdesc/{seqdesc.c => seqlocaldesc.c} | 20 +- src/backend/access/sequence/Makefile | 2 +- src/backend/access/sequence/meson.build | 2 + src/backend/access/sequence/seqlocalam.c | 634 ++++++++++++++++++ src/backend/access/sequence/seqlocalxlog.c | 82 +++ 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 + src/bin/pg_waldump/t/001_basic.pl | 2 +- 14 files changed, 816 insertions(+), 613 deletions(-) create mode 100644 src/include/access/seqlocalam.h rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%) create mode 100644 src/backend/access/sequence/seqlocalam.c create mode 100644 src/backend/access/sequence/seqlocalxlog.c diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h index 78e6b908c6..fa94beb6ed 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, btree_identify, btree_xlog PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL, hash_mask, NULL) PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, 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, NULL, seq_mask, NULL) +PG_RMGR(RM_SEQ_LOCAL_ID, "SequenceLocal", seq_local_redo, seq_local_desc, seq_local_identify, NULL, NULL, seq_local_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, NULL, brin_mask, NULL) PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL, NULL, NULL) diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h new file mode 100644 index 0000000000..225fb9a2cb --- /dev/null +++ b/src/include/access/seqlocalam.h @@ -0,0 +1,58 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.h + * Local sequence access method. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/access/seqlocalam.h + * + *------------------------------------------------------------------------- + */ +#ifndef SEQLOCALAM_H +#define SEQLOCALAM_H + +#include "access/xlogreader.h" +#include "storage/relfilelocator.h" +#include "utils/rel.h" + +/* XLOG stuff */ +#define XLOG_SEQ_LOCAL_LOG 0x00 + +typedef struct xl_seq_local_rec +{ + RelFileLocator locator; + /* SEQUENCE TUPLE DATA FOLLOWS AT THE END */ +} xl_seq_local_rec; + +/* + * The "special area" of a local sequence's buffer page looks like this. + */ +#define SEQ_LOCAL_MAGIC 0x1717 + +typedef struct seq_local_magic +{ + uint32 magic; +} seq_local_magic; + +extern void seq_local_redo(XLogReaderState *record); +extern void seq_local_desc(StringInfo buf, XLogReaderState *record); +extern const char *seq_local_identify(uint8 info); +extern void seq_local_mask(char *page, BlockNumber blkno); + +/* access routines */ +extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last); +extern const char *seq_local_get_table_am(void); +extern void seq_local_init(Relation rel, int64 last_value, bool is_called); +extern void seq_local_setval(Relation rel, int64 next, bool iscalled); +extern void seq_local_reset(Relation rel, int64 startv, bool is_called, + bool reset_state); +extern void seq_local_get_state(Relation rel, int64 *last_value, + bool *is_called); +extern void seq_local_change_persistence(Relation rel, + char newrelpersistence); + +#endif /* SEQLOCALAM_H */ diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile index cd95eec37f..e5900ed77a 100644 --- a/src/backend/access/rmgrdesc/Makefile +++ b/src/backend/access/rmgrdesc/Makefile @@ -24,7 +24,7 @@ OBJS = \ relmapdesc.o \ replorigindesc.o \ rmgrdesc_utils.o \ - seqdesc.o \ + seqlocaldesc.o \ smgrdesc.o \ spgdesc.o \ standbydesc.o \ diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build index e8b7a65fc7..e390d385bf 100644 --- a/src/backend/access/rmgrdesc/meson.build +++ b/src/backend/access/rmgrdesc/meson.build @@ -17,7 +17,7 @@ rmgr_desc_sources = files( 'relmapdesc.c', 'replorigindesc.c', 'rmgrdesc_utils.c', - 'seqdesc.c', + 'seqlocaldesc.c', 'smgrdesc.c', 'spgdesc.c', 'standbydesc.c', diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/seqlocaldesc.c similarity index 64% rename from src/backend/access/rmgrdesc/seqdesc.c rename to src/backend/access/rmgrdesc/seqlocaldesc.c index cf0e02ded5..409c67335e 100644 --- a/src/backend/access/rmgrdesc/seqdesc.c +++ b/src/backend/access/rmgrdesc/seqlocaldesc.c @@ -1,44 +1,44 @@ /*------------------------------------------------------------------------- * - * seqdesc.c - * rmgr descriptor routines for commands/sequence.c + * seqlocaldesc.c + * rmgr descriptor routines for sequence/seqlocal.c * * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * src/backend/access/rmgrdesc/seqdesc.c + * src/backend/access/rmgrdesc/seqlocaldesc.c * *------------------------------------------------------------------------- */ #include "postgres.h" -#include "commands/sequence.h" +#include "access/seqlocalam.h" void -seq_desc(StringInfo buf, XLogReaderState *record) +seq_local_desc(StringInfo buf, XLogReaderState *record) { char *rec = XLogRecGetData(record); uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - xl_seq_rec *xlrec = (xl_seq_rec *) rec; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) rec; - if (info == XLOG_SEQ_LOG) + if (info == XLOG_SEQ_LOCAL_LOG) appendStringInfo(buf, "rel %u/%u/%u", xlrec->locator.spcOid, xlrec->locator.dbOid, xlrec->locator.relNumber); } const char * -seq_identify(uint8 info) +seq_local_identify(uint8 info) { const char *id = NULL; switch (info & ~XLR_INFO_MASK) { - case XLOG_SEQ_LOG: - id = "LOG"; + case XLOG_SEQ_LOCAL_LOG: + id = "SEQ_LOCAL_LOG"; break; } diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile index 9f9d31f542..a15ceec1c0 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index 1843aed38d..4d24f900b6 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -1,5 +1,7 @@ # Copyright (c) 2022-2024, PostgreSQL Global Development Group backend_sources += files( + 'seqlocalam.c', + 'seqlocalxlog.c', 'sequence.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c new file mode 100644 index 0000000000..b0a0d72966 --- /dev/null +++ b/src/backend/access/sequence/seqlocalam.c @@ -0,0 +1,634 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.c + * Local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalam.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/multixact.h" +#include "access/seqlocalam.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 SEQ_LOCAL_LOG_VALS 32 + +/* Format of tuples stored in heap table associated to local sequences */ +typedef struct FormData_pg_seq_local_data +{ + int64 last_value; + int64 log_cnt; + bool is_called; +} FormData_pg_seq_local_data; + +typedef FormData_pg_seq_local_data *Form_pg_seq_local_data; + +/* + * Columns of a local sequence relation + */ +#define SEQ_LOCAL_COL_LASTVAL 1 +#define SEQ_LOCAL_COL_LOG 2 +#define SEQ_LOCAL_COL_CALLED 3 + +#define SEQ_LOCAL_COL_FIRSTCOL SEQ_LOCAL_COL_LASTVAL +#define SEQ_LOCAL_COL_LASTCOL SEQ_LOCAL_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_LOCAL_LOG_VALS 32 + +static Form_pg_seq_local_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 tuple + * + * *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_seq_local_data +read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + seq_local_magic *sm; + Form_pg_seq_local_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SEQ_LOCAL_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_pg_seq_local_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 == RELPERSISTENCE_UNLOGGED) + { + SMgrRelation srel; + + srel = 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 content + */ +static void +fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + seq_local_magic *sm; + OffsetNumber offnum; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + sm->magic = SEQ_LOCAL_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) tuple->t_data, tuple->t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_nextval() + * + * Allocate a new value for a local sequence, based on the sequence + * configuration. + */ +int64 +seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + int64 result; + int64 fetch; + int64 next; + int64 rescnt = 0; + int64 log; + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + Page page; + bool logit = false; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + page = BufferGetPage(buf); + + *last = next = result = seq->last_value; + fetch = cache; + log = 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_LOCAL_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 = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = true; + } + else + { + XLogRecPtr redoptr = GetRedoRecPtr(); + + if (PageGetLSN(page) <= redoptr) + { + /* last update of seq was before checkpoint */ + fetch = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = 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 >= 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 = minv; + } + else + next += incby; + } + else + { + /* descending sequence */ + if ((minv < 0 && next < minv - incby) || + (minv >= 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 = maxv; + } + else + next += incby; + } + fetch--; + if (rescnt < cache) + { + log--; + rescnt++; + *last = next; + if (rescnt == 1) /* if it's first result - */ + result = next; /* it's what to return */ + } + } + + log -= fetch; /* adjust for any unfetched numbers */ + Assert(log >= 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_seq_local_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 = next; + seq->is_called = true; + seq->log_cnt = 0; + + xlrec.locator = rel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + /* Now update sequence tuple to the intended final state */ + seq->last_value = *last; /* last fetched number */ + seq->is_called = true; + seq->log_cnt = log; /* how much is logged */ + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + return result; +} + +/* + * seq_local_get_table_am() + * + * Return the table access method used by this sequence. + */ +const char * +seq_local_get_table_am(void) +{ + return "heap"; +} + +/* + * seq_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 + * the information guessed from pg_sequences. This is the first tuple + * inserted after the relation has been created, filling in its heap + * table. + */ +void +seq_local_init(Relation rel, int64 last_value, bool is_called) +{ + Datum value[SEQ_LOCAL_COL_LASTCOL]; + bool null[SEQ_LOCAL_COL_LASTCOL]; + List *elts = NIL; + List *atcmds = NIL; + ListCell *lc; + TupleDesc tupdesc; + HeapTuple tuple; + + /* + * Create relation (and fill value[] and null[] for the initial tuple). + */ + for (int i = SEQ_LOCAL_COL_FIRSTCOL; i <= SEQ_LOCAL_COL_LASTCOL; i++) + { + ColumnDef *coldef = NULL; + + switch (i) + { + case SEQ_LOCAL_COL_LASTVAL: + coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatumFast(last_value); + break; + case SEQ_LOCAL_COL_LOG: + coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatum(0); + break; + case SEQ_LOCAL_COL_CALLED: + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + value[i - 1] = BoolGetDatum(is_called); + break; + } + + coldef->is_not_null = true; + null[i - 1] = false; + elts = lappend(elts, coldef); + } + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, value, null); + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_setval() + * + * Callback for setval(). + */ +void +seq_local_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->last_value = next; /* last fetched number */ + seq->is_called = iscalled; + seq->log_cnt = 0; + + MarkBufferDirty(buf); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel)) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + Page page = BufferGetPage(buf); + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_reset() + * + * Perform a hard reset on the local sequence, rewriting its heap data + * entirely. + */ +void +seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) +{ + Form_pg_seq_local_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 = 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 = (Form_pg_seq_local_data) GETSTRUCT(tuple); + seq->last_value = startv; + seq->is_called = is_called; + if (reset_state) + seq->log_cnt = 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 == InvalidTransactionId); + Assert(rel->rd_rel->relminmxid == InvalidMultiXactId); + + /* + * Insert the modified tuple into the new storage file. + */ + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_get_state() + * + * Retrieve the state of a local sequence. + */ +void +seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + *last_value = seq->last_value; + *is_called = seq->is_called; + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_change_persistence() + * + * Persistence change for the local sequence Relation. + */ +void +seq_local_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/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c new file mode 100644 index 0000000000..db0ad969db --- /dev/null +++ b/src/backend/access/sequence/seqlocalxlog.c @@ -0,0 +1,82 @@ +/*------------------------------------------------------------------------- + * + * seqlocalxlog.c + * WAL replay logic for local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalxlog.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/bufmask.h" +#include "access/seqlocalam.h" +#include "access/xlogutils.h" +#include "storage/block.h" + +/* + * Mask a Sequence page before performing consistency checks on it. + */ +void +seq_local_mask(char *page, BlockNumber blkno) +{ + mask_page_lsn_and_checksum(page); + + mask_unused_space(page); +} + +void +seq_local_redo(XLogReaderState *record) +{ + XLogRecPtr lsn = record->EndRecPtr; + uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; + Buffer buffer; + Page page; + Page localpage; + char *item; + Size itemsz; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record); + seq_local_magic *sm; + + if (info != XLOG_SEQ_LOCAL_LOG) + elog(PANIC, "seq_redo: unknown op code %u", info); + + buffer = XLogInitBufferForRedo(record, 0); + page = (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 = (Page) palloc(BufferGetPageSize(buffer)); + + PageInit(localpage, BufferGetPageSize(buffer), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(localpage); + sm->magic = SEQ_LOCAL_MAGIC; + + item = (char *) xlrec + sizeof(xl_seq_local_rec); + itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec); + + if (PageAddItem(localpage, (Item) item, itemsz, + FirstOffsetNumber, false, false) == InvalidOffsetNumber) + elog(PANIC, "seq_local_redo: failed to add item to page"); + + PageSetLSN(localpage, lsn); + + memcpy(page, localpage, BufferGetPageSize(buffer)); + MarkBufferDirty(buffer); + UnlockReleaseBuffer(buffer); + + pfree(localpage); +} diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c index 3e2f1d4a23..9c22ef315f 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/seqlocalam.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/sequence.c index d6f2fc3ce0..6d712e76e2 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,6 +16,7 @@ #include "access/bufmask.h" #include "access/htup_details.h" +#include "access/seqlocalam.h" #include "access/multixact.h" #include "access/relation.h" #include "access/sequence.h" @@ -50,23 +51,6 @@ #include "utils/varlena.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 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 current * session. This is needed to hold onto nextval/currval state. (We can't @@ -96,13 +80,9 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */ */ static SeqTableData *last_used_seq = NULL; -static void fill_seq_with_data(Relation rel, HeapTuple tuple); -static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber 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_identity, 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 = NIL; - List *atcmds = NIL; - ListCell *lc; Datum pgs_values[Natts_pg_sequence]; bool pgs_nulls[Natts_pg_sequence]; - int i; /* * 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); - /* - * Create relation (and fill value[] and null[] for the tuple) - */ - for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) - { - ColumnDef *coldef = NULL; - - switch (i) - { - case SEQ_COL_LASTVAL: - coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(last_value); - break; - case SEQ_COL_LOG: - coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatum((int64) 0); - break; - case SEQ_COL_CALLED: - coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); - value[i - 1] = BoolGetDatum(false); - break; - } - - coldef->is_not_null = true; - null[i - 1] = false; - - elts = lappend(elts, coldef); - } - stmt->relation = seq->sequence; stmt->inhRelations = NIL; stmt->constraints = NIL; @@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) */ stmt->tableElts = NIL; + /* + * Initial relation has no attributes, these can be added later via the + * "init" AM callback. + */ + stmt->tableElts = NIL; + address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); - /* Add all the attributes to the sequence */ - foreach(lc, elts) - { - AlterTableCmd *atcmd; - - atcmd = makeNode(AlterTableCmd); - atcmd->subtype = AT_AddColumnToSequence; - atcmd->def = (Node *) lfirst(lc); - atcmds = lappend(atcmds, atcmd); - } - - /* - * No recursion needed. Note that EventTriggerAlterTableStart() should - * have been called. - */ - AlterTableInternal(RelationGetRelid(rel), atcmds, false); - CommandCounterIncrement(); - - tupDesc = RelationGetDescr(rel); - - /* now initialize the sequence's data */ - tuple = heap_form_tuple(tupDesc, value, null); - fill_seq_with_data(rel, tuple); + /* now initialize the sequence table structure and its data */ + seq_local_init(rel, last_value, is_called); /* 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); pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid)); if (!HeapTupleIsValid(pgstuple)) @@ -315,40 +240,8 @@ ResetSequence(Oid seq_relid) startv = pgsform->seqstart; ReleaseSysCache(pgstuple); - /* - * Copy the existing sequence tuple. - */ - tuple = 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 = (Form_pg_sequence_data) GETSTRUCT(tuple); - seq->last_value = startv; - seq->is_called = false; - seq->log_cnt = 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 == InvalidTransactionId); - Assert(seq_rel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - fill_seq_with_data(seq_rel, tuple); + /* Sequence state is forcibly reset here. */ + seq_local_reset(seq_rel, startv, false, true); /* 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); } -/* - * 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 == RELPERSISTENCE_UNLOGGED) - { - SMgrRelation srel; - - srel = 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 content - */ -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 = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, - EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); - Assert(BufferGetBlockNumber(buf) == 0); - - page = BufferGetPage(buf); - - PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(page); - sm->magic = SEQ_MAGIC; - - /* Now insert sequence tuple */ - - /* - * Since VACUUM does not process sequences, we have to force the tuple to - * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, - InvalidOffsetNumber, false, false); - if (offnum != FirstOffsetNumber) - elog(ERROR, "failed to add sequence tuple to page"); - - /* XLOG stuff */ - if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = rel->rd_locator; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) tuple->t_data, tuple->t_len); - - recptr = 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 = false; bool is_called; int64 last_value; - HeapTuple newdatatuple; /* Open and lock sequence, and check for ownership along the way. */ relid = RangeVarGetRelidExtended(stmt->sequence, @@ -507,16 +296,8 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) seqform = (Form_pg_sequence) GETSTRUCT(seqtuple); - /* 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 = heap_copytuple(&datatuple); - newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); - last_value = newdataform->last_value; - is_called = newdataform->is_called; - - UnlockReleaseBuffer(buf); + /* Read sequence data */ + seq_local_get_state(seqrel, &last_value, &is_called); /* 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(); - /* - * 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 == InvalidTransactionId); - Assert(seqrel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - newdataform->last_value = last_value; - newdataform->is_called = is_called; - if (reset_state) - newdataform->log_cnt = 0; - fill_seq_with_data(seqrel, newdatatuple); + seq_local_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) { SeqTable elm; Relation seqrel; - Buffer buf; - HeapTupleData seqdatatuple; init_sequence(relid, &elm, &seqrel); @@ -589,10 +346,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - (void) read_seq_tuple(seqrel, &buf, &seqdatatuple); - RelationSetNewRelfilenumber(seqrel, newrelpersistence); - fill_seq_with_data(seqrel, &seqdatatuple); - UnlockReleaseBuffer(buf); + seq_local_change_persistence(seqrel, newrelpersistence); 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 = 0; + int64 result; bool cycle; - bool logit = false; /* open and lock sequence */ init_sequence(relid, &elm, &seqrel); @@ -717,105 +462,9 @@ nextval_internal(Oid relid, bool check_permissions) cycle = pgsform->seqcycle; ReleaseSysCache(pgstuple); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - page = BufferGetPage(buf); - - last = next = result = seq->last_value; - fetch = cache; - log = 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 = log = fetch + SEQ_LOG_VALS; - logit = true; - } - else - { - XLogRecPtr redoptr = GetRedoRecPtr(); - - if (PageGetLSN(page) <= redoptr) - { - /* last update of seq was before checkpoint */ - fetch = log = fetch + SEQ_LOG_VALS; - logit = 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 >= 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 = minv; - } - else - next += incby; - } - else - { - /* descending sequence */ - if ((minv < 0 && next < minv - incby) || - (minv >= 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 = maxv; - } - else - next += incby; - } - fetch--; - if (rescnt < cache) - { - log--; - rescnt++; - last = next; - if (rescnt == 1) /* if it's first result - */ - result = next; /* it's what to return */ - } - } - - log -= fetch; /* adjust for any unfetched numbers */ - Assert(log >= 0); + /* retrieve next value from the access method */ + result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -825,69 +474,6 @@ nextval_internal(Oid relid, bool check_permissions) last_used_seq = elm; - /* - * 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 = next; - seq->is_called = true; - seq->log_cnt = 0; - - xlrec.locator = seqrel->rd_locator; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - /* Now update sequence tuple to the intended final state */ - seq->last_value = last; /* last fetched number */ - seq->is_called = true; - seq->log_cnt = log; /* how much is logged */ - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); - sequence_close(seqrel, NoLock); 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()"); - /* lock page buffer and read tuple */ - seq = 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(); - /* ready to change the on-disk (or really, in-buffer) tuple */ - START_CRIT_SECTION(); - - seq->last_value = next; /* last fetched number */ - seq->is_called = iscalled; - seq->log_cnt = 0; - - MarkBufferDirty(buf); - - /* XLOG stuff */ - if (RelationNeedsWAL(seqrel)) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - Page page = BufferGetPage(buf); - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = seqrel->rd_locator; - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); + /* Call the access method callback */ + seq_local_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1208,62 +759,6 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) } -/* - * Given an opened sequence relation, lock the page buffer and find the tuple - * - * *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 = ReadBuffer(rel, 0); - LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); - - page = BufferGetPage(*buf); - sm = (sequence_magic *) PageGetSpecialPointer(page); - - if (sm->magic != SEQ_MAGIC) - elog(ERROR, "bad magic number in sequence \"%s\": %08X", - RelationGetRelationName(rel), sm->magic); - - lp = PageGetItemId(page, FirstOffsetNumber); - Assert(ItemIdIsNormal(lp)); - - /* Note we currently only bother to set these two fields of *seqdatatuple */ - seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); - seqdatatuple->t_len = 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) != InvalidTransactionId) - { - HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); - seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; - seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; - MarkBufferDirtyHint(*buf, true); - } - - seq = (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; @@ -1842,12 +1334,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) errmsg("permission denied for sequence %s", RelationGetRelationName(seqrel)))); - seq = read_seq_tuple(seqrel, &buf, &seqtuple); - - is_called = seq->is_called; - last_value = seq->last_value; - - UnlockReleaseBuffer(buf); + seq_local_get_state(seqrel, &last_value, &is_called); sequence_close(seqrel, NoLock); values[0] = BoolGetDatum(is_called); @@ -1855,57 +1342,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } - -void -seq_redo(XLogReaderState *record) -{ - XLogRecPtr lsn = record->EndRecPtr; - uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - Buffer buffer; - Page page; - Page localpage; - char *item; - Size itemsz; - xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record); - sequence_magic *sm; - - if (info != XLOG_SEQ_LOG) - elog(PANIC, "seq_redo: unknown op code %u", info); - - buffer = XLogInitBufferForRedo(record, 0); - page = (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 = (Page) palloc(BufferGetPageSize(buffer)); - - PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(localpage); - sm->magic = SEQ_MAGIC; - - item = (char *) xlrec + sizeof(xl_seq_rec); - itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec); - - if (PageAddItem(localpage, (Item) item, itemsz, - FirstOffsetNumber, false, false) == 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) last_used_seq = 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..8d1195de26 100644 --- a/src/bin/pg_waldump/.gitignore +++ b/src/bin/pg_waldump/.gitignore @@ -10,13 +10,13 @@ /gistdesc.c /hashdesc.c /heapdesc.c +/seqlocaldesc.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..2fccc7a4e2 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/seqlocalam.h" #include "access/multixact.h" #include "access/nbtxlog.h" #include "access/rmgr.h" diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl index 082cb8a589..8a36f49947 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 +SequenceLocal SPGist BRIN CommitTs -- 2.43.0 [text/x-diff] v5-0005-Sequence-access-methods-backend-support.patch (61.0K, ../../[email protected]/6-v5-0005-Sequence-access-methods-backend-support.patch) download | inline diff: From 2448144361ef88729a7d56619f90348aca28e7a7 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Thu, 25 Apr 2024 17:15:20 +0900 Subject: [PATCH v5 5/8] Sequence access methods - backend support The "seqlocal" sequence AM is now plugged in as a handler in the relcache, and a set of callbacks in sequenceam.h. --- src/include/access/seqlocalam.h | 15 -- src/include/access/sequenceam.h | 181 ++++++++++++++++++ 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/meson.build | 1 + src/backend/access/sequence/seqlocalam.c | 41 +++- 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/psql/describe.c | 2 + src/bin/psql/tab-complete.c | 4 +- src/test/regress/expected/create_am.out | 55 ++++-- src/test/regress/expected/opr_sanity.out | 12 ++ src/test/regress/expected/psql.out | 96 +++++----- src/test/regress/sql/create_am.sql | 24 ++- src/test/regress/sql/opr_sanity.sql | 10 + src/tools/pgindent/typedefs.list | 5 +- 38 files changed, 682 insertions(+), 169 deletions(-) create mode 100644 src/include/access/sequenceam.h create mode 100644 src/backend/access/sequence/sequenceamapi.c diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h index 225fb9a2cb..21936511ac 100644 --- a/src/include/access/seqlocalam.h +++ b/src/include/access/seqlocalam.h @@ -15,7 +15,6 @@ #include "access/xlogreader.h" #include "storage/relfilelocator.h" -#include "utils/rel.h" /* XLOG stuff */ #define XLOG_SEQ_LOCAL_LOG 0x00 @@ -41,18 +40,4 @@ extern void seq_local_desc(StringInfo buf, XLogReaderState *record); extern const char *seq_local_identify(uint8 info); extern void seq_local_mask(char *page, BlockNumber blkno); -/* access routines */ -extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last); -extern const char *seq_local_get_table_am(void); -extern void seq_local_init(Relation rel, int64 last_value, bool is_called); -extern void seq_local_setval(Relation rel, int64 next, bool iscalled); -extern void seq_local_reset(Relation rel, int64 startv, bool is_called, - bool reset_state); -extern void seq_local_get_state(Relation rel, int64 *last_value, - bool *is_called); -extern void seq_local_change_persistence(Relation rel, - char newrelpersistence); - #endif /* SEQLOCALAM_H */ diff --git a/src/include/access/sequenceam.h b/src/include/access/sequenceam.h new file mode 100644 index 0000000000..ac48c8b468 --- /dev/null +++ b/src/include/access/sequenceam.h @@ -0,0 +1,181 @@ +/*------------------------------------------------------------------------- + * + * sequenceam.h + * POSTGRES sequence access method definitions. + * + * + * Portions Copyright (c) 1996-2024, 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 "seqlocal" + +/* 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 value + * allocated. The result returned is the next value of the sequence computed. + */ +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); + +#endif /* SEQUENCEAM_H */ diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat index db87490282..d991a77604 100644 --- a/src/include/catalog/pg_am.dat +++ b/src/include/catalog/pg_am.dat @@ -15,6 +15,9 @@ { oid => '2', oid_symbol => 'HEAP_TABLE_AM_OID', descr => 'heap table access method', amname => 'heap', amhandler => 'heap_tableam_handler', amtype => 't' }, +{ oid => '8048', oid_symbol => 'LOCAL_SEQUENCE_AM_OID', + descr => 'local sequence access method', + amname => 'seqlocal', amhandler => 'seq_local_sequenceam_handler', amtype => 's' }, { oid => '403', oid_symbol => 'BTREE_AM_OID', descr => 'b-tree index access method', amname => 'btree', amhandler => 'bthandler', amtype => '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 */ #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) == RELKIND_TOASTVALUE || \ (relkind) == RELKIND_MATVIEW) +/* + * Relation kinds with a sequence access method (rd_sequenceam). + */ +#define RELKIND_HAS_SEQUENCE_AM(relkind) \ + ((relkind) == RELKIND_SEQUENCE) + extern int errdetail_relkind_not_supported(char relkind); #endif /* EXPOSE_TO_CLIENT_CODE */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 1a8c8ebba5..f012afbbde 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -913,6 +913,12 @@ prorettype => 'table_am_handler', proargtypes => 'internal', prosrc => 'heap_tableam_handler' }, +# Sequence access method handlers +{ oid => '8209', descr => 'local sequence access method handler', + proname => 'seq_local_sequenceam_handler', provolatile => 'v', + prorettype => 'sequence_am_handler', proargtypes => 'internal', + prosrc => 'seq_local_sequenceam_handler' }, + # Index access method handlers { oid => '330', descr => 'btree index access method handler', proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler', @@ -7627,6 +7633,13 @@ { oid => '327', descr => 'I/O', proname => 'index_am_handler_out', prorettype => 'cstring', proargtypes => 'index_am_handler', prosrc => 'index_am_handler_out' }, +{ oid => '8207', descr => 'I/O', + proname => 'sequence_am_handler_in', proisstrict => 'f', + prorettype => 'sequence_am_handler', proargtypes => 'cstring', + prosrc => 'sequence_am_handler_in' }, +{ oid => '8208', descr => 'I/O', + proname => 'sequence_am_handler_out', prorettype => 'cstring', + proargtypes => 'sequence_am_handler', prosrc => 'sequence_am_handler_out' }, { oid => '3311', descr => 'I/O', proname => 'tsm_handler_in', proisstrict => 'f', prorettype => 'tsm_handler', proargtypes => 'cstring', prosrc => '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 => 'P', typinput => 'index_am_handler_in', typoutput => 'index_am_handler_out', typreceive => '-', typsend => '-', typalign => 'i' }, +{ oid => '8210', + descr => 'pseudo-type for the result of a sequence AM handler function', + typname => 'sequence_am_handler', typlen => '4', typbyval => 't', + typtype => 'p', typcategory => 'P', typinput => 'sequence_am_handler_in', + typoutput => 'sequence_am_handler_out', typreceive => '-', typsend => '-', + typalign => 'i' }, { oid => '3310', descr => 'pseudo-type for the result of a tablesample method function', typname => 'tsm_handler', typlen => '4', typbyval => 't', typtype => '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); diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.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" -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); -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 = [ '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 fdcebc268e..5910768cf7 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; 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 **extra, 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 **extra, + 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; + /* + * 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/sequence/Makefile index a15ceec1c0..62006165a1 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = seqlocalam.o seqlocalxlog.o sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o sequenceamapi.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index 4d24f900b6..ea91990552 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -4,4 +4,5 @@ backend_sources += files( 'seqlocalam.c', 'seqlocalxlog.c', 'sequence.c', + 'sequenceamapi.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c index b0a0d72966..2e524c9e99 100644 --- a/src/backend/access/sequence/seqlocalam.c +++ b/src/backend/access/sequence/seqlocalam.c @@ -17,6 +17,7 @@ #include "access/multixact.h" #include "access/seqlocalam.h" +#include "access/sequenceam.h" #include "access/xact.h" #include "access/xloginsert.h" #include "access/xlogutils.h" @@ -24,6 +25,7 @@ #include "commands/tablecmds.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "utils/builtins.h" /* @@ -230,10 +232,10 @@ fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) * Allocate a new value for a local sequence, based on the sequence * configuration. */ -int64 +static int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last) + int64 minv, int64 cache, bool cycle, + int64 *last) { int64 result; int64 fetch; @@ -417,7 +419,7 @@ seq_local_nextval(Relation rel, int64 incby, int64 maxv, * * Return the table access method used by this sequence. */ -const char * +static const char * seq_local_get_table_am(void) { return "heap"; @@ -432,7 +434,7 @@ seq_local_get_table_am(void) * inserted after the relation has been created, filling in its heap * table. */ -void +static void seq_local_init(Relation rel, int64 last_value, bool is_called) { Datum value[SEQ_LOCAL_COL_LASTCOL]; @@ -499,7 +501,7 @@ seq_local_init(Relation rel, int64 last_value, bool is_called) * * Callback for setval(). */ -void +static void seq_local_setval(Relation rel, int64 next, bool iscalled) { Buffer buf; @@ -547,7 +549,7 @@ seq_local_setval(Relation rel, int64 next, bool iscalled) * Perform a hard reset on the local sequence, rewriting its heap data * entirely. */ -void +static void seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) { Form_pg_seq_local_data seq; @@ -600,7 +602,7 @@ seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) * * Retrieve the state of a local sequence. */ -void +static void seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) { Buffer buf; @@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) * * Persistence change for the local sequence Relation. */ -void +static void seq_local_change_persistence(Relation rel, char newrelpersistence) { Buffer buf; @@ -632,3 +634,24 @@ seq_local_change_persistence(Relation rel, char newrelpersistence) fill_seq_with_data(rel, &seqdatatuple); UnlockReleaseBuffer(buf); } + +/* ------------------------------------------------------------------------ + * Definition of the local sequence access method. + * ------------------------------------------------------------------------ + */ +static const SequenceAmRoutine seq_local_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = seq_local_get_table_am, + .init = seq_local_init, + .nextval = seq_local_nextval, + .setval = seq_local_setval, + .reset = seq_local_reset, + .get_state = seq_local_get_state, + .change_persistence = seq_local_change_persistence +}; + +Datum +seq_local_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&seq_local_methods); +} diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/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 sequences - * (in contrast to other relation types like indexes). + * (in contrast to other relation types like indexes) that are independent + * of individual sequence access methods. * *------------------------------------------------------------------------- */ diff --git a/src/backend/access/sequence/sequenceamapi.c b/src/backend/access/sequence/sequenceamapi.c new file mode 100644 index 0000000000..dd1a60d827 --- /dev/null +++ b/src/backend/access/sequence/sequenceamapi.c @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------- + * + * sequenceamapi.c + * general sequence access method routines + * + * Portions Copyright (c) 1996-2024, 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 = 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 = OidFunctionCall0(amhandler); + routine = (SequenceAmRoutine *) DatumGetPointer(datum); + + if (routine == NULL || !IsA(routine, SequenceAmRoutine)) + elog(ERROR, "sequence access method handler %u did not return a SequenceAmRoutine 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 != NULL); + Assert(routine->init != NULL); + Assert(routine->nextval != NULL); + Assert(routine->setval != NULL); + Assert(routine->reset != NULL); + Assert(routine->get_state != NULL); + Assert(routine->change_persistence != 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 = SearchSysCache1(AMOID, + ObjectIdGetDatum(amoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", amoid); + aform = (Form_pg_am) GETSTRUCT(tuple); + Assert(aform->amtype == AMTYPE_SEQUENCE); + amhandleroid = 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 == '\0') + { + GUC_check_errdetail("%s cannot be empty.", + "default_sequence_access_method"); + return false; + } + + if (strlen(*newval) >= 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 != InvalidOid) + { + if (!OidIsValid(get_sequence_am_oid(*newval, true))) + { + /* + * When source == 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 == 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 != RELKIND_TOASTVALUE) + if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != 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 @@ #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); } +/* + * 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 = INDEX_AM_HANDLEROID; break; + case AMTYPE_SEQUENCE: + expectedType = SEQUENCE_AM_HANDLEROID; + break; case AMTYPE_TABLE: expectedType = TABLE_AM_HANDLEROID; break; diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 6d712e76e2..57db43501e 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,10 +16,10 @@ #include "access/bufmask.h" #include "access/htup_details.h" -#include "access/seqlocalam.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 = NIL; stmt->constraints = NIL; stmt->options = NIL; + stmt->accessMethod = seq->accessMethod ? pstrdup(seq->accessMethod) : NULL; stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; @@ -173,7 +174,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) rel = sequence_open(seqoid, AccessExclusiveLock); /* now initialize the sequence table structure and its data */ - seq_local_init(rel, last_value, is_called); + sequence_init(rel, last_value, is_called); /* process OWNED BY if given */ if (owned_by) @@ -241,7 +242,7 @@ ResetSequence(Oid seq_relid) ReleaseSysCache(pgstuple); /* Sequence state is forcibly reset here. */ - seq_local_reset(seq_rel, startv, false, true); + sequence_reset(seq_rel, startv, false, true); /* 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 = (Form_pg_sequence) GETSTRUCT(seqtuple); /* Read sequence data */ - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); /* 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(); - seq_local_reset(seqrel, last_value, is_called, reset_state); + sequence_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -346,7 +347,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - seq_local_change_persistence(seqrel, newrelpersistence); + sequence_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -463,8 +464,8 @@ nextval_internal(Oid relid, bool check_permissions) ReleaseSysCache(pgstuple); /* retrieve next value from the access method */ - result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, - &last); + result = sequence_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -618,7 +619,7 @@ do_setval(Oid relid, int64 next, bool iscalled) GetTopTransactionId(); /* Call the access method callback */ - seq_local_setval(seqrel, next, iscalled); + sequence_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1334,7 +1335,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) errmsg("permission denied for sequence %s", RelationGetRelationName(seqrel)))); - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); sequence_close(seqrel, NoLock); values[0] = BoolGetDatum(is_called); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index ad24777bb8..f8a771d1ff 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" @@ -964,14 +965,18 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, } /* - * 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 != NULL) { - Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE); - accessMethodId = get_table_am_oid(stmt->accessMethod, false); + Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE || + RELKIND_HAS_SEQUENCE_AM(relkind)); + if (RELKIND_HAS_SEQUENCE_AM(relkind)) + accessMethodId = get_sequence_am_oid(stmt->accessMethod, false); + else + accessMethodId = get_table_am_oid(stmt->accessMethod, false); } else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE) { @@ -984,6 +989,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId)) accessMethodId = get_table_am_oid(default_table_access_method, false); } + else if (RELKIND_HAS_SEQUENCE_AM(relkind)) + { + accessMethodId = get_sequence_am_oid(default_sequence_access_method, false); + } /* * 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 = \ 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 = 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 = 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, List *aliases, Node *query); %type <str> copy_file_name access_method_clause attr_name table_access_method_clause name cursor_name file_name + sequence_access_method_clause cluster_index_specification %type <list> func_name handler_name qual_Op qual_all_Op subquery_Op @@ -4929,23 +4930,26 @@ RefreshMatViewStmt: CreateSeqStmt: CREATE OptTemp SEQUENCE qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); - $4->relpersistence = $2; n->sequence = $4; n->options = $5; + n->accessMethod = $6; n->ownerId = InvalidOid; n->if_not_exists = false; $$ = (Node *) n; } | CREATE OptTemp SEQUENCE IF_P NOT EXISTS qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); $7->relpersistence = $2; n->sequence = $7; n->options = $8; + n->accessMethod = $9; n->ownerId = InvalidOid; n->if_not_exists = true; $$ = (Node *) n; @@ -4982,6 +4986,11 @@ OptParenthesizedSeqOptList: '(' SeqOptList ')' { $$ = $2; } | /*EMPTY*/ { $$ = NIL; } ; +sequence_access_method_clause: + USING name { $$ = $2; } + | /*EMPTY*/ { $$ = NULL; } + ; + SeqOptList: SeqOptElem { $$ = list_make1($1); } | SeqOptList SeqOptElem { $$ = lappend($1, $2); } ; @@ -5978,6 +5987,7 @@ CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name am_type: INDEX { $$ = AMTYPE_INDEX; } + | SEQUENCE { $$ = AMTYPE_SEQUENCE; } | TABLE { $$ = AMTYPE_TABLE; } ; 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, ColumnDef *column, seqstmt->sequence->relpersistence = cxt->rel ? cxt->rel->rd_rel->relpersistence : cxt->relation->relpersistence; seqstmt->options = seqoptions; + seqstmt->accessMethod = NULL; /* * 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/pseudotypes.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/relcache.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 == RELKIND_INDEX || relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) RelationInitIndexAccessInfo(relation); - else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || - relation->rd_rel->relkind == RELKIND_SEQUENCE) + else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)) RelationInitTableAccessMethod(relation); else if (relation->rd_rel->relkind == 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 == InvalidOid); @@ -1811,17 +1815,9 @@ RelationInitTableAccessMethod(Relation relation) HeapTuple tuple; Form_pg_am aform; - if (relation->rd_rel->relkind == 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 == InvalidOid); - relation->rd_amhandler = 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); } +/* + * 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 != InvalidOid); + relation->rd_amhandler = GetSequenceAmRoutineId(relation->rd_rel->relam); + + /* + * Now we can fetch the sequence AM's API struct. + */ + relation->rd_sequenceam = GetSequenceAmRoutine(relation->rd_amhandler); + + /* + * From the sequence AM, set its expected table access method. + */ + tableam_name = sequence_get_table_am(relation); + tableam_oid = get_table_am_oid(tableam_name, false); + + tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(tableam_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", + tableam_oid); + aform = (Form_pg_am) GETSTRUCT(tuple); + tableam_handler = aform->amhandler; + ReleaseSysCache(tuple); + + relation->rd_tableam = GetTableAmRoutine(tableam_handler); +} + /* * formrdesc * @@ -3670,14 +3709,17 @@ RelationBuildLocalRelation(const char *relname, rel->rd_rel->relam = accessmtd; /* - * 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); - if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(relkind)) RelationInitTableAccessMethod(rel); + else if (relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); /* * Okay to insert into the relcache hash table. @@ -4292,13 +4334,21 @@ RelationCacheInitializePhase3(void) /* Reload tableam data if needed */ if (relation->rd_tableam == NULL && - (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE)) + (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))) { RelationInitTableAccessMethod(relation); Assert(relation->rd_tableam != NULL); restart = true; } + else if (relation->rd_sequenceam == NULL && + relation->rd_rel->relkind == RELKIND_SEQUENCE) + { + RelationInitSequenceAccessMethod(relation); + Assert(relation->rd_sequenceam != NULL); + + restart = true; + } /* Release hold on the relation */ RelationDecrementReferenceCount(relation); @@ -6319,8 +6369,10 @@ load_relcache_init_file(bool shared) nailed_rels++; /* Load table AM data */ - if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || rel->rd_rel->relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind)) RelationInitTableAccessMethod(rel); + else if (rel->rd_rel->relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); Assert(rel->rd_index == NULL); Assert(rel->rd_indextuple == NULL); @@ -6332,6 +6384,7 @@ load_relcache_init_file(bool shared) Assert(rel->rd_supportinfo == NULL); Assert(rel->rd_indoption == NULL); Assert(rel->rd_indcollation == NULL); + Assert(rel->rd_sequenceam == NULL); Assert(rel->rd_opcoptions == NULL); } diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_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[] = check_default_table_access_method, NULL, NULL }, + { + {"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default sequence access method for new sequences."), + 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/utils/misc/postgresql.conf.sample index 2166ea4a87..6ee5b5bfca 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -703,6 +703,7 @@ # error #search_path = '"$user", public' # schema names #row_security = on +#default_sequence_access_method = 'seqlocal' #default_table_access_method = 'heap' #default_tablespace = '' # a tablespace name, '' uses the default #default_toast_compression = 'pglz' # 'pglz' or 'lz4' 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 verbose) "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")); 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 <name> 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/expected/create_am.out index 9762c332ce..b6566e27d5 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) PARTITION 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 = 'seqlocal'; 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; @@ -519,18 +517,18 @@ FROM pg_class AS pc LEFT JOIN pg_am AS pa ON (pa.oid = pc.relam) WHERE pc.relname LIKE 'tableam_%_heapx' ORDER BY 3, 1, 2; - relkind | amname | relname ----------+--------+----------------------------- - f | | tableam_fdw_heapx - r | heap2 | tableam_parted_1_heapx - r | heap | tableam_parted_2_heapx - p | | tableam_parted_heapx - S | | tableam_seq_heapx - r | heap2 | tableam_tbl_heapx - r | heap2 | tableam_tblas_heapx - m | heap2 | tableam_tblmv_heapx - r | heap2 | tableam_tblselectinto_heapx - v | | tableam_view_heapx + relkind | amname | relname +---------+----------+----------------------------- + f | | tableam_fdw_heapx + r | heap2 | tableam_parted_1_heapx + r | heap | tableam_parted_2_heapx + p | | tableam_parted_heapx + S | seqlocal | tableam_seq_heapx + r | heap2 | tableam_tbl_heapx + r | heap2 | tableam_tblas_heapx + m | heap2 | tableam_tblmv_heapx + r | heap2 | tableam_tblselectinto_heapx + v | | tableam_view_heapx (10 rows) -- don't want to keep those tables, nor the default @@ -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_dump support +-- Checks for sequence access methods +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_local_sequenceam_handler; +-- Create and use sequence +CREATE SEQUENCE test_seqam USING local2; +SELECT nextval('test_seqam'::regclass); + nextval +--------- + 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/expected/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 = a1.amhandler AND a1.amtype = 't' AND -----+--------+-----+--------- (0 rows) +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + oid | amname | oid | proname +-----+--------+-----+--------- +(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..e64e50bbb9 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -4962,31 +4962,33 @@ Indexes: -- check printing info about access methods \dA List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA * List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA h* List of access methods @@ -5011,32 +5013,34 @@ List of access methods \dA: extra argument "bar" ignored \dA+ - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ * - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ h* List of access methods diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql index 825aed325e..472289994e 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 FROM tableam_tbl_heap2; -- CREATE VIEW doesn't support USING CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2; --- 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 tableam_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; --- 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 = 'seqlocal'; 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; -- we intentionally leave the objects created above alive, to verify pg_dump support + +-- Checks for sequence access methods + +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_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 = a1.amhandler AND a1.amtype = 't' AND OR p1.pronargs != 1 OR p1.proargtypes[0] != 'internal'::regtype); +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + -- **************** pg_amop **************** -- Look for illegal values in pg_amop fields diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 90a7c389b2..fedf2a77d7 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 @@ -3541,6 +3542,7 @@ lineno_t list_sort_comparator local_relopt local_relopts +local_sequence_magic local_source locale_t locate_agg_of_level_context @@ -3805,7 +3807,6 @@ save_buffer scram_state scram_state_enum sem_t -sequence_magic set_join_pathlist_hook_type set_rel_pathlist_hook_type shm_mq @@ -4020,6 +4021,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 @@ -4031,7 +4033,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 -- 2.43.0 [text/x-diff] v5-0006-Sequence-access-methods-dump-restore-support.patch (22.3K, ../../[email protected]/7-v5-0006-Sequence-access-methods-dump-restore-support.patch) download | inline diff: From 0043f858105559de904fd6b4f1efcabcc832c5de Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Thu, 25 Apr 2024 17:28:27 +0900 Subject: [PATCH v5 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_backup_archiver.c index c6c101c118..f5d2b2fa4f 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -174,6 +174,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) dopt->outputSuperuser = ropt->superuser; dopt->outputCreateDB = ropt->createDB; dopt->outputNoOwner = ropt->noOwner; + dopt->outputNoSequenceAm = ropt->noSequenceAm; dopt->outputNoTableAm = ropt->noTableAm; dopt->outputNoTablespaces = ropt->noTablespace; dopt->disable_triggers = ropt->disable_triggers; @@ -1224,6 +1225,7 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId, newToc->tag = pg_strdup(opts->tag); newToc->namespace = opts->namespace ? pg_strdup(opts->namespace) : NULL; newToc->tablespace = opts->tablespace ? pg_strdup(opts->tablespace) : NULL; + newToc->sequenceam = opts->sequenceam ? pg_strdup(opts->sequenceam) : NULL; newToc->tableam = opts->tableam ? pg_strdup(opts->tableam) : NULL; newToc->relkind = opts->relkind; newToc->owner = opts->owner ? pg_strdup(opts->owner) : NULL; @@ -2376,6 +2378,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, AH->currUser = NULL; /* unknown */ AH->currSchema = NULL; /* ditto */ + AH->currSequenceAm = NULL; /* ditto */ AH->currTablespace = NULL; /* ditto */ AH->currTableAm = NULL; /* ditto */ @@ -2605,6 +2608,7 @@ WriteToc(ArchiveHandle *AH) WriteStr(AH, te->copyStmt); WriteStr(AH, te->namespace); WriteStr(AH, te->tablespace); + WriteStr(AH, te->sequenceam); WriteStr(AH, te->tableam); WriteInt(AH, te->relkind); WriteStr(AH, te->owner); @@ -2709,6 +2713,9 @@ ReadToc(ArchiveHandle *AH) if (AH->version >= K_VERS_1_10) te->tablespace = ReadStr(AH); + if (AH->version >= K_VERS_1_17) + te->sequenceam = ReadStr(AH); + if (AH->version >= K_VERS_1_14) te->tableam = ReadStr(AH); @@ -3364,6 +3371,9 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname) free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; + free(AH->currTableAm); AH->currTableAm = NULL; @@ -3526,6 +3536,57 @@ _selectTablespace(ArchiveHandle *AH, const char *tablespace) destroyPQExpBuffer(qry); } +/* + * Set the proper default_sequence_access_method value for the sequence. + */ +static void +_selectSequenceAccessMethod(ArchiveHandle *AH, const char *sequenceam) +{ + RestoreOptions *ropt = AH->public.ropt; + PQExpBuffer cmd; + const char *want, + *have; + + /* do nothing in --no-sequence-access-method mode */ + if (ropt->noSequenceAm) + return; + + have = AH->currSequenceAm; + want = sequenceam; + + if (!want) + return; + + if (have && strcmp(want, have) == 0) + return; + + cmd = createPQExpBuffer(); + appendPQExpBuffer(cmd, + "SET default_sequence_access_method = %s;", + fmtId(want)); + + if (RestoringToDB(AH)) + { + PGresult *res; + + res = PQexec(AH->connection, cmd->data); + + if (!res || PQresultStatus(res) != 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 = pg_strdup(want); +} + /* * Set the proper default_table_access_method value for the table. */ @@ -3735,6 +3796,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData) _becomeOwner(AH, te); _selectOutputSchema(AH, te->namespace); _selectTablespace(AH, te->tablespace); + _selectSequenceAccessMethod(AH, te->sequenceam); if (te->relkind != RELKIND_PARTITIONED_TABLE) _selectTableAccessMethod(AH, te->tableam); @@ -4237,6 +4299,8 @@ restore_toc_entries_prefork(ArchiveHandle *AH, TocEntry *pending_list) AH->currUser = NULL; free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; free(AH->currTablespace); AH->currTablespace = NULL; free(AH->currTableAm); @@ -4976,6 +5040,7 @@ CloneArchive(ArchiveHandle *AH) clone->connCancel = NULL; clone->currUser = NULL; clone->currSchema = NULL; + clone->currSequenceAm = NULL; clone->currTableAm = NULL; clone->currTablespace = NULL; @@ -5035,6 +5100,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_backup_archiver.h index ce5ed1dd39..cdfebcd340 100644 --- a/src/bin/pg_dump/pg_backup_archiver.h +++ b/src/bin/pg_dump/pg_backup_archiver.h @@ -71,10 +71,11 @@ #define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0) /* BLOB METADATA entries * and multiple BLOBS, * relkind */ +#define K_VERS_1_17 MAKE_ARCHIVE_VERSION(1, 17, 0) /* add sequenceam */ /* 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_VERS_REV) @@ -322,6 +323,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 */ @@ -353,6 +355,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 relkind; /* relation kind, only for TABLE tags */ char *owner; @@ -394,6 +397,7 @@ typedef struct _archiveOpts const char *tag; const char *namespace; const char *tablespace; + const char *sequenceam; const char *tableam; char relkind; const char *owner; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 54c4f75861..c1c7e40e20 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 = dopt.outputSuperuser; ropt->createDB = dopt.outputCreateDB; ropt->noOwner = dopt.outputNoOwner; + ropt->noSequenceAm = dopt.outputNoSequenceAm; ropt->noTableAm = dopt.outputNoTableAm; ropt->noTablespace = dopt.outputNoTablespaces; ropt->disable_triggers = 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 assignments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); + printf(_(" --no-sequence-access-method do not sequence table access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); @@ -13317,6 +13320,9 @@ dumpAccessMethod(Archive *fout, const AccessMethodInfo *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; @@ -17559,7 +17565,8 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) *maxv, *minv, *cache, - *seqtype; + *seqtype, + *seqam; bool cycled; bool is_ascending; int64 default_minv, @@ -17573,13 +17580,35 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) qseqname = pg_strdup(fmtId(tbinfo->dobj.name)); - if (fout->remoteVersion >= 100000) + if (fout->remoteVersion >= 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 = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam) " + "WHERE s.seqrelid = '%u'::oid", + tbinfo->dobj.catId.oid); + } + else if (fout->remoteVersion >= 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, " + "'seqlocal' AS seqam " "FROM pg_catalog.pg_sequence " "WHERE seqrelid = '%u'::oid", tbinfo->dobj.catId.oid); @@ -17595,7 +17624,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, 'seqlocal' as seqam FROM %s", fmtQualifiedDumpable(tbinfo)); } @@ -17614,6 +17643,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) minv = PQgetvalue(res, 0, 4); cache = PQgetvalue(res, 0, 5); cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0); + seqam = PQgetvalue(res, 0, 7); /* Calculate default limits for a sequence of this type */ is_ascending = (incby[0] != '-'); @@ -17745,6 +17775,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) ARCHIVE_OPTS(.tag = tbinfo->dobj.name, .namespace = tbinfo->dobj.namespace->dobj.name, .owner = tbinfo->rolname, + .sequenceam = seqam, .description = "SEQUENCE", .section = SECTION_PRE_DATA, .createStmt = 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 = 0; static int disable_triggers = 0; static int if_exists = 0; static int inserts = 0; +static int no_sequence_access_method = 0; static int no_table_access_method = 0; static int no_tablespaces = 0; static int use_setsessauth = 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 assignments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); printf(_(" --no-sync do not wait for changes to be written safely to disk\n")); + printf(_(" --no-sequence-access-method do not dump sequence access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\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 = 0; static int if_exists = 0; static int no_data_for_failed_tables = 0; + static int outputNoSequenceAm = 0; static int outputNoTableAm = 0; static int outputNoTablespaces = 0; static int use_setsessauth = 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 = disable_triggers; opts->enable_row_security = enable_row_security; opts->noDataForFailedTables = no_data_for_failed_tables; + opts->noSequenceAm = outputNoSequenceAm; opts->noTableAm = outputNoTableAm; opts->noTablespace = outputNoTablespaces; opts->use_setsessauth = 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 access methods\n")); printf(_(" --no-table-access-method do not restore table access methods\n")); printf(_(" --no-tablespaces do not restore tablespace assignments\n")); printf(_(" --section=SECTION restore named section (pre-data, 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_dump.pl index 7085053a2d..0f5bc6f812 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 = ( 'postgres', ], }, + no_sequence_access_method => { + dump_cmd => [ + 'pg_dump', '--no-sync', + "--file=$tempdir/no_sequence_access_method.sql", + '--no-sequence-access-method', 'postgres', + ], + }, no_table_access_method => { dump_cmd => [ 'pg_dump', '--no-sync', @@ -711,6 +718,7 @@ my %full_runs = ( no_large_objects => 1, no_owner => 1, no_privs => 1, + no_sequence_access_method => 1, no_table_access_method => 1, pg_dumpall_dbprivs => 1, pg_dumpall_exclude => 1, @@ -3855,9 +3863,7 @@ my %tests = ( \QCREATE INDEX measurement_city_id_logdate_idx ON ONLY dump_test.measurement USING\E /xm, like => { - %full_runs, - %dump_test_schema_runs, - section_post_data => 1, + %full_runs, %dump_test_schema_runs, section_post_data => 1, }, unlike => { exclude_dump_test_schema => 1, @@ -4526,6 +4532,18 @@ my %tests = ( }, }, + 'CREATE ACCESS METHOD regress_test_sequence_am' => { + create_order => 11, + create_sql => + 'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;', + regexp => qr/^ + \QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;\E + \n/xm, + like => { + %full_runs, section_pre_data => 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 = ( }, }, + + # 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' => { + create_order => 12, + create_sql => ' + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_0 USING seqlocal; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_1 USING regress_sequence_am; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_2 USING seqlocal;', + regexp => qr/^ + \QSET default_sequence_access_method = 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 => { + %full_runs, %dump_test_schema_runs, section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + no_sequence_access_method => 1, + only_dump_measurement => 1, + }, + }, + 'CREATE MATERIALIZED VIEW regress_pg_dump_matview_am' => { create_order => 13, create_sql => ' @@ -4783,10 +4830,8 @@ $node->command_fails_like( ############################################################## # Test dumping pg_catalog (for research -- cannot be reloaded) -$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'); ######################################### # 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 08d775379f..0261a0fcbf 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -1104,6 +1104,23 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + + <para> + This option is ignored when emitting an archive (non-text) output + file. For the archive formats, you can specify the option when you + call <command>pg_restore</command>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml 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 <replaceable class="parameter">PATTERN</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml 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 </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> -- 2.43.0 [text/x-diff] v5-0007-Sequence-access-methods-core-documentation.patch (9.6K, ../../[email protected]/8-v5-0007-Sequence-access-methods-core-documentation.patch) download | inline diff: From 19d13ccc50a165f22b41a3fe381ec67744660c41 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:55:56 +0900 Subject: [PATCH v5 7/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' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-default-sequence-access-method" xreflabel="default_sequence_access_method"> + <term><varname>default_sequence_access_method</varname> (<type>string</type>) + <indexterm> + <primary><varname>default_sequence_access_method</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + This parameter specifies the default sequence access method to use when + creating sequences if the <command>CREATE SEQUENCE</command> + command does not explicitly specify an access method. The default is + <literal>local</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-default-tablespace" xreflabel="default_tablespace"> <term><varname>default_tablespace</varname> (<type>string</type>) <indexterm> 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 @@ <!ENTITY planstats SYSTEM "planstats.sgml"> <!ENTITY tableam SYSTEM "tableam.sgml"> <!ENTITY indexam SYSTEM "indexam.sgml"> +<!ENTITY sequenceam SYSTEM "sequenceam.sgml"> <!ENTITY nls SYSTEM "nls.sgml"> <!ENTITY plhandler SYSTEM "plhandler.sgml"> <!ENTITY fdwhandler SYSTEM "fdwhandler.sgml"> 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 <replaceable class="parameter">name</replaceable> <listitem> <para> This clause specifies the type of access method to define. - Only <literal>TABLE</literal> and <literal>INDEX</literal> - are supported at present. + Only <literal>TABLE</literal>, <literal>SEQUENCE</literal> and + <literal>INDEX</literal> are supported at present. </para> </listitem> </varlistentry> @@ -77,12 +77,15 @@ CREATE ACCESS METHOD <replaceable class="parameter">name</replaceable> declared to take a single argument of type <type>internal</type>, and its return type depends on the type of access method; for <literal>TABLE</literal> access methods, it must - be <type>table_am_handler</type> and for <literal>INDEX</literal> - access methods, it must be <type>index_am_handler</type>. + be <type>table_am_handler</type>; for <literal>INDEX</literal> + access methods, it must be <type>index_am_handler</type>; + for <literal>SEQUENCE</literal>, it must be + <type>sequence_am_handler</type>; 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 <xref linkend="tableam"/> and the index access method - API is described in <xref linkend="indexam"/>. + is described in <xref linkend="tableam"/>, the index access method + API is described in <xref linkend="indexam"/> and the sequence access + method is described in <xref linkend="sequenceam"/>. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/create_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 ] <replaceab [ MINVALUE <replaceable class="parameter">minvalue</replaceable> | NO MINVALUE ] [ MAXVALUE <replaceable class="parameter">maxvalue</replaceable> | NO MAXVALUE ] [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ [ NO ] CYCLE ] [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ] + [ USING <replaceable class="parameter">access_method</replaceable> ] </synopsis> </refsynopsisdiv> @@ -261,6 +262,17 @@ SELECT * FROM <replaceable>name</replaceable>; </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>USING</literal> <replaceable class="parameter">access_method</replaceable></term> + <listitem> + <para> + The <literal>USING</literal> option specifies which sequence access + method will be used when generating the sequence numbers. The default + is <literal>local</literal>. + </para> + </listitem> + </varlistentry> </variablelist> </refsect1> 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 @@ +<!-- doc/src/sgml/sequenceam.sgml --> + +<chapter id="sequenceam"> + <title>Sequence Access Method Interface Definition</title> + + <indexterm> + <primary>Sequence Access Method</primary> + </indexterm> + <indexterm> + <primary>sequenceam</primary> + <secondary>Sequence Access Method</secondary> + </indexterm> + + <para> + This chapter explains the interface between the core + <productname>PostgreSQL</productname> system and <firstterm>sequence access + methods</firstterm>, 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. + </para> + + <para> + Each sequence access method is described by a row in the + <link linkend="catalog-pg-am"><structname>pg_am</structname></link> system + catalog. The <structname>pg_am</structname> entry specifies a name and a + <firstterm>handler function</firstterm> for the sequence access method. These + entries can be created and deleted using the + <xref linkend="sql-create-access-method"/> and + <xref linkend="sql-drop-access-method"/> SQL commands. + </para> + + <para> + A sequence access method handler function must be declared to accept a single + argument of type <type>internal</type> and to return the pseudo-type + <type>sequence_am_handler</type>. The argument is a dummy value that simply + 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 + <structname>SequenceAmRoutine</structname>, which contains everything that the + core code needs to know to make use of the sequence access method. The return + value needs to be of server lifetime, which is typically achieved by + defining it as a <literal>static const</literal> variable in global + scope. The <structname>SequenceAmRoutine</structname> struct, also called the + access method's <firstterm>API struct</firstterm>, 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 + <structname>SequenceAmRoutine</structname> structure (with comments inside + 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 <ulink url="https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/include/access/sequenceam.h;hb=HEAD"> + <filename>src/include/access/sequenceam.h</filename></ulink> file. + </para> + + <para> + Currently, the way a sequence access method stores data is fairly + unconstrained, and it is possible to use a predefined + <link linkend="tableam">Table Access Method</link> to store sequence + data. + </para> + + <para> + For crash safety, a sequence access method can use + <link linkend="wal"><acronym>WAL</acronym></link>, or a custom + implementation. + If <acronym>WAL</acronym> is chosen, either + <link linkend="generic-wal">Generic WAL Records</link> can be used, or a + <link linkend="custom-rmgr">Custom WAL Resource Manager</link> can be + implemented. + </para> + + <para> + Any developer of a new <literal>sequence access method</literal> can refer to + the existing <literal>local</literal> implementation present in + <filename>src/backend/access/sequence/local.c</filename> for details of + its implementation. + </para> +</chapter> -- 2.43.0 [text/x-diff] v5-0008-snowflake-Add-sequence-AM-based-on-it.patch (28.0K, ../../[email protected]/9-v5-0008-snowflake-Add-sequence-AM-based-on-it.patch) download | inline diff: From 07b626d6641e7bb871caf7f4e35262097bc988d0 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 26 Apr 2024 15:09:00 +0900 Subject: [PATCH v5 8/8] snowflake: Add sequence AM based on it This includes documentation and a basic implementation, though this is in a very early stage. --- doc/src/sgml/contrib.sgml | 1 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/snowflake.sgml | 100 ++++ contrib/Makefile | 1 + contrib/meson.build | 1 + contrib/snowflake/.gitignore | 3 + contrib/snowflake/Makefile | 19 + contrib/snowflake/expected/snowflake.out | 73 +++ contrib/snowflake/meson.build | 34 ++ contrib/snowflake/snowflake--1.0.sql | 21 + contrib/snowflake/snowflake.c | 570 +++++++++++++++++++++++ contrib/snowflake/snowflake.control | 5 + contrib/snowflake/sql/snowflake.sql | 29 ++ 13 files changed, 858 insertions(+) create mode 100644 doc/src/sgml/snowflake.sgml create mode 100644 contrib/snowflake/.gitignore create mode 100644 contrib/snowflake/Makefile create mode 100644 contrib/snowflake/expected/snowflake.out create mode 100644 contrib/snowflake/meson.build create mode 100644 contrib/snowflake/snowflake--1.0.sql create mode 100644 contrib/snowflake/snowflake.c create mode 100644 contrib/snowflake/snowflake.control create mode 100644 contrib/snowflake/sql/snowflake.sql diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml index 44639a8dca..e7b8871e8a 100644 --- a/doc/src/sgml/contrib.sgml +++ b/doc/src/sgml/contrib.sgml @@ -166,6 +166,7 @@ CREATE EXTENSION <replaceable>extension_name</replaceable>; &seg; &sepgsql; &contrib-spi; + &snowflake; &sslinfo; &tablefunc; &tcn; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 4e1218aa89..3b9e38e320 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -156,6 +156,7 @@ <!ENTITY seg SYSTEM "seg.sgml"> <!ENTITY contrib-spi SYSTEM "contrib-spi.sgml"> <!ENTITY sepgsql SYSTEM "sepgsql.sgml"> +<!ENTITY snowflake SYSTEM "snowflake.sgml"> <!ENTITY sslinfo SYSTEM "sslinfo.sgml"> <!ENTITY tablefunc SYSTEM "tablefunc.sgml"> <!ENTITY tcn SYSTEM "tcn.sgml"> diff --git a/doc/src/sgml/snowflake.sgml b/doc/src/sgml/snowflake.sgml new file mode 100644 index 0000000000..060699e7ec --- /dev/null +++ b/doc/src/sgml/snowflake.sgml @@ -0,0 +1,100 @@ +<!-- doc/src/sgml/snowflake.sgml --> + +<sect1 id="snowflake" xreflabel="snowflake"> + <title>snowflake — sequence access method</title> + + <indexterm zone="snowflake"> + <primary>snowflake</primary> + </indexterm> + + <para> + <literal>snowflake</literal> provides a sequence access method based on + <ulink url="https://en.wikipedia.org/wiki/Snowflake_ID">Snowflake IDs</ulink>. + </para> + + <para> + A Snowflake ID (or snowflake) is a unique 64-bit identifier made of three + components: + <itemizedlist spacing="compact"> + <listitem><para>41 bits for a timestamp, epoch-adjusted in milli-seconds</para></listitem> + <listitem><para>10 bits for machine ID</para></listitem> + <listitem><para>12 bits for a sequence number</para></listitem> + </itemizedlist> + </para> + + <sect2 id="snowflake-functions"> + <title>Functions</title> + + <variablelist> + <varlistentry> + <term> + <function>snowflake_get(raw int8) returns record</function> + <indexterm> + <primary>snowflake_get</primary> + <secondary>function</secondary> + </indexterm> + </term> + + <listitem> + <para> + Returns a record made of the timestamp in milli-seconds, the machine ID + and the sequence number for a single snowflake ID. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-parameters"> + <title>Configuration Parameters</title> + + <variablelist> + <varlistentry> + <term> + <varname>snowflake.machine_id</varname> + <indexterm> + <primary><varname>snowflake.machine_id</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Machine ID assigned to the snowflake IDs used in the sequence. The + default value is <literal>1</literal>. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-examples"> + <title>Examples</title> + + <para> + This is an example of creating a snowflake sequence: + </para> + +<programlisting> +CREATE SEQUENCE snowflake_seq USING snowflake; +</programlisting> + + <para> + Similarly to the default sequence access method, snowflake sequences + can be queried as a table: + </para> + +<programlisting> + =# SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) +=# SELECT to_timestamp(time_ms / 1000), machine, counter + FROM snowflake_get(nextval('snowflake_seq')); + to_timestamp | machine | counter +------------------------+---------+--------- + 2024-04-26 14:28:26+09 | 1 | 3 +(1 row) +</programlisting> + </sect2> + +</sect1> diff --git a/contrib/Makefile b/contrib/Makefile index abd780f277..f5d1b568b2 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -42,6 +42,7 @@ SUBDIRS = \ pg_walinspect \ postgres_fdw \ seg \ + snowflake \ spi \ tablefunc \ tcn \ diff --git a/contrib/meson.build b/contrib/meson.build index 14a8906865..4f2603e7e9 100644 --- a/contrib/meson.build +++ b/contrib/meson.build @@ -57,6 +57,7 @@ subdir('pg_walinspect') subdir('postgres_fdw') subdir('seg') subdir('sepgsql') +subdir('snowflake') subdir('spi') subdir('sslinfo') # start-scripts doesn't contain build products diff --git a/contrib/snowflake/.gitignore b/contrib/snowflake/.gitignore new file mode 100644 index 0000000000..44d119cfcc --- /dev/null +++ b/contrib/snowflake/.gitignore @@ -0,0 +1,3 @@ +# Generated subdirectories +/log/ +/results/ diff --git a/contrib/snowflake/Makefile b/contrib/snowflake/Makefile new file mode 100644 index 0000000000..fa5b48d565 --- /dev/null +++ b/contrib/snowflake/Makefile @@ -0,0 +1,19 @@ +# contrib/snowflake/Makefile + +MODULES = snowflake + +EXTENSION = snowflake +DATA = snowflake--1.0.sql + +REGRESS = snowflake + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/snowflake +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/snowflake/expected/snowflake.out b/contrib/snowflake/expected/snowflake.out new file mode 100644 index 0000000000..b7e469bf73 --- /dev/null +++ b/contrib/snowflake/expected/snowflake.out @@ -0,0 +1,73 @@ +CREATE EXTENSION snowflake; +CREATE SEQUENCE snowflake_seq USING snowflake; +SET snowflake.machine_id = 2000; -- error +ERROR: 2000 is outside the valid range for parameter "snowflake.machine_id" (0 .. 1023) +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(lastval()); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 3 | t +(1 row) + +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + relpersistence +---------------- + u +(1 row) + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; + machine | counter +---------+--------- + 4 | 2 + 4 | 3 + 4 | 4 + 4 | 5 + 4 | 6 + 4 | 7 + 4 | 8 + 4 | 9 + 4 | 10 + 4 | 11 +(10 rows) + +DROP TABLE snowflake_tab; +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; diff --git a/contrib/snowflake/meson.build b/contrib/snowflake/meson.build new file mode 100644 index 0000000000..567669eea7 --- /dev/null +++ b/contrib/snowflake/meson.build @@ -0,0 +1,34 @@ +# Copyright (c) 2024, PostgreSQL Global Development Group + +snowflake_sources = files( + 'snowflake.c', +) + +if host_system == 'windows' + snowflake_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'snowflake', + '--FILEDESC', 'snowflake - sequence access method',]) +endif + +snowflake = shared_module('snowflake', + snowflake_sources, + kwargs: contrib_mod_args, +) +contrib_targets += bloom + +install_data( + 'snowflake.control', + 'snowflake--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'snowflake', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'snowflake', + ], + }, +} diff --git a/contrib/snowflake/snowflake--1.0.sql b/contrib/snowflake/snowflake--1.0.sql new file mode 100644 index 0000000000..bcb9d754f1 --- /dev/null +++ b/contrib/snowflake/snowflake--1.0.sql @@ -0,0 +1,21 @@ +/* contrib/snowflake/snowflake--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION snowflake" to load this file. \quit + +CREATE FUNCTION snowflake_sequenceam_handler(internal) + RETURNS sequence_am_handler + AS 'MODULE_PATHNAME' + LANGUAGE C; + +CREATE ACCESS METHOD snowflake + TYPE SEQUENCE HANDLER snowflake_sequenceam_handler; +COMMENT ON ACCESS METHOD snowflake IS 'snowflake sequence access method'; + +CREATE FUNCTION snowflake_get(IN raw int8, + OUT time_ms int8, + OUT machine int4, + OUT counter int4) + RETURNS record + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT diff --git a/contrib/snowflake/snowflake.c b/contrib/snowflake/snowflake.c new file mode 100644 index 0000000000..be4c4039ec --- /dev/null +++ b/contrib/snowflake/snowflake.c @@ -0,0 +1,570 @@ +/*------------------------------------------------------------------------- + * + * snowflake.c + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * contrib/snowflake/snowflake.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <sys/time.h> + +#include "access/generic_xlog.h" +#include "access/sequenceam.h" +#include "access/xact.h" +#include "catalog/storage_xlog.h" +#include "commands/tablecmds.h" +#include "fmgr.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "storage/bufmgr.h" +#include "utils/guc.h" +#include "utils/numeric.h" + +PG_MODULE_MAGIC; + +/* "special area" of a snowflake's buffer page. */ +#define SNOWFLAKE_MAGIC 0x01 + +typedef struct snowflake_magic +{ + uint32 magic; +} snowflake_magic; + +/* ----------------------------------------------------------------------- + * Snowflake ID are 64-bit based, with the following structure: + * - 41 bits for an epoch-based timestamp, in milli-seconds. + * - 10 bits for a machine ID. + * - 12 bits for a sequence counter. + * + * The timestamp can be cut to an offset. The machine ID is controlled + * by a superuser GUC. Sequence properties apply to the sequence counter, + * as the other two are environment-dependent. + * ----------------------------------------------------------------------- + */ + +/* + * Helper routines to convert a snowflake ID from/to an int64. + */ +#define SNOWFLAKE_COUNTER_MASK 0x0000000000000FFF /* 12 bits */ +#define SNOWFLAKE_COUNTER_SHIFT 0 +#define SNOWFLAKE_MACHINE_ID_MASK 0x00000000000003FF /* 10 bits */ +#define SNOWFLAKE_MACHINE_ID_SHIFT 12 /* counter */ +#define SNOWFLAKE_TIMESTAMP_MASK 0x000001FFFFFFFFFF /* 41 bits */ +#define SNOWFLAKE_TIMESTAMP_SHIFT 22 /* machine ID + counter sizes */ + +typedef struct snowflake_id +{ + uint32 count; + uint32 machine; + uint64 time_ms; +} snowflake_id; + +#define snowflake_id_to_int64(id, raw) { \ + raw = (((id).count) & SNOWFLAKE_COUNTER_MASK) << SNOWFLAKE_COUNTER_SHIFT | \ + (((id).machine) & SNOWFLAKE_MACHINE_ID_MASK) << SNOWFLAKE_MACHINE_ID_SHIFT | \ + (((id).time_ms) & SNOWFLAKE_TIMESTAMP_MASK) << SNOWFLAKE_TIMESTAMP_SHIFT; \ +} + +#define int64_to_snowflake_id(raw, id) { \ + (id).count = ((raw) >> SNOWFLAKE_COUNTER_SHIFT) & SNOWFLAKE_COUNTER_MASK; \ + (id).machine = ((raw) >> SNOWFLAKE_MACHINE_ID_SHIFT) & SNOWFLAKE_MACHINE_ID_MASK; \ + (id).time_ms = ((raw) >> SNOWFLAKE_TIMESTAMP_SHIFT) & SNOWFLAKE_TIMESTAMP_MASK; \ +} + +/* + * Format of tuples stored in heap table associated to snowflake sequence. + */ +typedef struct FormData_snowflake_data +{ + /* enough to cover 12 bits of the internal counter */ + int16 count; + bool is_called; +} FormData_snowflake_data; + +typedef FormData_snowflake_data *Form_snowflake_data; + +/* + * Columns of a snowflake sequence relation. + */ +#define SNOWFLAKE_COL_COUNT 1 +#define SNOWFLAKE_COL_CALLED 2 + +#define SNOWFLAKE_COLS 2 + +/* GUC parameter */ +static int snowflake_machine_id = 1; + +PG_FUNCTION_INFO_V1(snowflake_sequenceam_handler); + +/* ----------------------------------------------------------------------- + * Interfaces for relation manipulation. + * ----------------------------------------------------------------------- + */ + +/* + * Initialize snowflake relation's fork with some data. + */ +static void +fill_snowflake_fork(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + snowflake_magic *sm; + OffsetNumber offnum; + GenericXLogState *state = NULL; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(snowflake_magic)); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + sm->magic = SNOWFLAKE_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= HEAP_XMAX_INVALID; + ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber); + + /* + * Initialize before entering in the critical section, as this does + * allocations. + */ + if (forkNum == INIT_FORKNUM) + state = GenericXLogStart(rel); + + START_CRIT_SECTION(); + + MarkBufferDirty(buf); + + offnum = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* + * Init forks have to be logged. These go through generic WAL records + * for simplicity's sake to save from the need of a custom RMGR. + */ + if (forkNum == INIT_FORKNUM) + { + (void) GenericXLogRegisterBuffer(state, buf, GENERIC_XLOG_FULL_IMAGE); + GenericXLogFinish(state); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * Initialize snowflake relation. + * + * This needs to handle both the initial and main forks. + */ +static void +fill_snowflake(Relation rel, HeapTuple tuple) +{ + SMgrRelation srel; + + Assert(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED); + + fill_snowflake_fork(rel, tuple, MAIN_FORKNUM); + + /* init fork */ + srel = smgropen(rel->rd_locator, INVALID_PROC_NUMBER); + smgrcreate(srel, INIT_FORKNUM, false); + log_smgrcreate(&rel->rd_locator, INIT_FORKNUM); + fill_snowflake_fork(rel, tuple, INIT_FORKNUM); + FlushRelationBuffers(rel); + smgrclose(srel); +} + +/* + * Read the current state of a snowflake sequence + * + * Given an opened sequence relation, lock the page buffer and find the tuple. + * + * *buf receives the reference to the pinned-and-ex-locked buffer. + * *seqdatatuple receives the reference to the sequence tuple proper. + * + * Returns value points to the data payload of the tuple. + */ +static Form_snowflake_data +read_snowflake(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + snowflake_magic *sm; + Form_snowflake_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SNOWFLAKE_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_snowflake_data) GETSTRUCT(seqdatatuple); + + return seq; +} + + +/* ------------------------------------------------------------------------ + * Callbacks for the snowflake 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 * +snowflake_sequenceam_get_table_am(void) +{ + return "heap"; +} + +/* + * snowflake_sequenceam_init + * + * Initialize relation of a snowflake sequence. This stores the sequence + * counter in an unlogged relation as timestamps ensure value unicity. + */ +static void +snowflake_sequenceam_init(Relation rel, int64 last_value, bool is_called) +{ + Datum values[SNOWFLAKE_COLS]; + bool nulls[SNOWFLAKE_COLS]; + int16 counter; + TupleDesc tupdesc; + HeapTuple tuple; + List *elts = NIL; + ListCell *lc; + ColumnDef *coldef = NULL; + AlterTableCmd *atcmd; + List *atcmds = NIL; + + /* Adjust last_value, depending on the defaults given */ + counter = ((int16) last_value) & SNOWFLAKE_COUNTER_MASK; + + /* + * Create unlogged relation with its attributes. + */ + coldef = makeColumnDef("count", INT2OID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + + foreach(lc, elts) + { + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + /* + * Switch the relation to be unlogged. This forces a rewrite, but + * the relation is empty so that's OK. + */ + RelationSetNewRelfilenumber(rel, RELPERSISTENCE_UNLOGGED); + + /* And insert its first tuple */ + values[0] = Int16GetDatum(counter); + nulls[0] = false; + values[1] = BoolGetDatum(is_called); + nulls[1] = false; + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, values, nulls); + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_nextval + * + * Return the next value for a snowflake sequence. + */ +static int64 +snowflake_sequenceam_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + Buffer buf; + Form_snowflake_data seq; + HeapTupleData seqdatatuple; + int64 result = 0; + snowflake_id id = {0}; + struct timeval tp; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* + * The logic here is quite simple, increment the counter until its + * threshold is reached and get back to the start. If the threshold + * is reached, wait 1ms to ensure a unique timestamp. There is no + * need to do a retry as the buffer is already locked. + */ + id.count = seq->count; + id.count++; + + if (id.count > (PG_INT16_MAX & SNOWFLAKE_COUNTER_MASK)) + { + /* + * Threshold reached, so wait a bit for force clock to a new + * timestamp. + */ + id.count = 1; + pg_usleep(1000L); /* 1ms */ + } + + /* Compute timestamp, with buffer locked */ + gettimeofday(&tp, NULL); + id.time_ms = (uint64) tp.tv_sec * 1000 + + tp.tv_usec / 1000; + + /* Machine ID */ + id.machine = snowflake_machine_id; + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->count = id.count; + seq->is_called = true; + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + /* Store the last value computed for lastval() */ + snowflake_id_to_int64(id, result); + *last = result; + return result; +} + +/* + * snowflake_sequenceam_setval + * + * Set the sequence value, manipulating only the sequence counter. + */ +static void +snowflake_sequenceam_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* Change the in-buffer tuple */ + START_CRIT_SECTION(); + seq->count = (next & SNOWFLAKE_COUNTER_MASK); + seq->is_called = iscalled; + MarkBufferDirty(buf); + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_get_state + * + * Return the last sequence counter value. + */ +static void +snowflake_sequenceam_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + seq = read_snowflake(rel, &buf, &seqdatatuple); + *last_value = seq->count; + *is_called = seq->is_called; + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_reset + * + * Reset the sequence, coming down to resetting its counter. + */ +static void +snowflake_sequenceam_reset(Relation rel, int64 startv, bool is_called, + bool reset_state) +{ + HeapTupleData seqdatatuple; + HeapTuple tuple; + Form_snowflake_data seq; + Buffer buf; + + /* lock buffer page and read tuple */ + (void) read_snowflake(rel, &buf, &seqdatatuple); + + /* copy the existing tuple */ + tuple = 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 = (Form_snowflake_data) GETSTRUCT(tuple); + seq->count = (startv & SNOWFLAKE_COUNTER_MASK); + seq->is_called = is_called; + + /* create new storage */ + RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence); + + /* insert the modified tuple into the page */ + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_change_persistence + * + * There is nothing to do here, the underneath relation has to remain + * unlogged and is set as such when creating the sequence. + */ +static void +snowflake_sequenceam_change_persistence(Relation rel, char newrelpersistence) +{ + /* Nothing to do here */ +} + +/* ------------------------------------------------------------------------ + * Definition of the snowflake sequence access method. + * ------------------------------------------------------------------------ + */ + +static const SequenceAmRoutine snowflake_sequenceam_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = snowflake_sequenceam_get_table_am, + .init = snowflake_sequenceam_init, + .nextval = snowflake_sequenceam_nextval, + .setval = snowflake_sequenceam_setval, + .get_state = snowflake_sequenceam_get_state, + .reset = snowflake_sequenceam_reset, + .change_persistence = snowflake_sequenceam_change_persistence +}; + +Datum +snowflake_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&snowflake_sequenceam_methods); +} + +/* Utility functions */ + +/* + * snowflake_get + * + * Return a tuple worth of snowflake ID data, in a readable shape. + */ +PG_FUNCTION_INFO_V1(snowflake_get); +Datum +snowflake_get(PG_FUNCTION_ARGS) +{ +#define SNOWFLAKE_GET_COLS 3 + int64 raw = PG_GETARG_INT64(0); + Datum *values; + bool *nulls; + TupleDesc tupdesc; + snowflake_id id; + + /* determine result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + int64_to_snowflake_id(raw, id); + + nulls = palloc0(sizeof(bool) * tupdesc->natts); + values = palloc0(sizeof(Datum) * tupdesc->natts); + + values[0] = Int64GetDatum(id.time_ms); + values[1] = Int32GetDatum(id.machine); + values[2] = Int32GetDatum(id.count); + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Entry point when loading extension. + */ +void +_PG_init(void) +{ + DefineCustomIntVariable("snowflake.machine_id", + "Machine ID to use with snowflake sequence.", + "Default value is 1.", + &snowflake_machine_id, + 1, 0, 1023, /* 10 bits as max */ + PGC_SUSET, + 0, NULL, NULL, NULL); +} diff --git a/contrib/snowflake/snowflake.control b/contrib/snowflake/snowflake.control new file mode 100644 index 0000000000..7b8c6089c2 --- /dev/null +++ b/contrib/snowflake/snowflake.control @@ -0,0 +1,5 @@ +# snowflake extension +comment = 'snowflake - sequence access method' +default_version = '1.0' +module_pathname = '$libdir/snowflake' +relocatable = true diff --git a/contrib/snowflake/sql/snowflake.sql b/contrib/snowflake/sql/snowflake.sql new file mode 100644 index 0000000000..395d166ba4 --- /dev/null +++ b/contrib/snowflake/sql/snowflake.sql @@ -0,0 +1,29 @@ +CREATE EXTENSION snowflake; + +CREATE SEQUENCE snowflake_seq USING snowflake; + +SET snowflake.machine_id = 2000; -- error +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(lastval()); +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; +DROP TABLE snowflake_tab; + +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; -- 2.43.0 [application/pgp-signature] signature.asc (833B, ../../[email protected]/10-signature.asc) download ^ permalink raw reply [nested|flat] 14+ messages in thread
* Re: Sequence Access Methods, round two @ 2024-06-20 06:12 Michael Paquier <[email protected]> parent: Michael Paquier <[email protected]> 0 siblings, 1 reply; 14+ messages in thread From: Michael Paquier @ 2024-06-20 06:12 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; +Cc: Tomas Vondra <[email protected]>; Peter Smith <[email protected]>; Postgres hackers <[email protected]> On Fri, Apr 26, 2024 at 03:21:29PM +0900, Michael Paquier wrote: > The next plan is to hopefully be able to trigger a discussion at the > next pgconf.dev at the end of May, but let's see how it goes. I am a bit behind an update of this thread, but there has been an unconference on the topic at the last pgconf.dev. This is based on my own notes written down after the session, so if there are gaps, feel free to correct me. The session was called "Sequences & Clusters", and was in two parts, with the first part covering this thread, and the second part covering the problem of sequences with logical replication for upgrade cases. I've taken a lot of time with the 1st part (sorry about that Amit K.!) still the second part has reached an agreement about what to do next there, and this is covered by this thread these days: https://www.postgresql.org/message-id/CAA4eK1LC%2BKJiAkSrpE_NwvNdidw9F2os7GERUeSxSKv71gXysQ%40mail.g... My overall feeling before this session was that I did not feel that folks grabbed the problem I was trying to solve, and, while it did not feel that the end of the session completely filled the gaps, and least folks finished with some idea of the reason why I've been trying something here. First, I have spoken for a few minutes about the use-cases I've been trying to solve, where parts of it involve Postgres-XC, an auto-proclaimed multi-master solution fork of Postgres, where sequences are handled by patching src/backend/commands/sequence.c to retrieve values from a GTM (global transaction manager, source of truth for value uniqueness shared by all the nodes), something I got my hands on between 2009~2012 (spoiler: people tend to like more scaling out clusters 12 years later). Then explained why Postgres is not good in this area. The original idea is that we want to be able for some applications to scale out Postgres across many hosts while making it transparent to the user's applications. By that, imagine a huge big box where users can connect to a single point, but underground any connection could involve a connection to a cluster of N PostgreSQL nodes, N being rather large (say N > 10k?). Why would we want that? One problem behind such configurations is that there is no way to make the values transparent for the application without applying schema changes (attribute defaults, UUIDs but these are large, for example), meaning that schemas cannot really be migrated as-they-are from one space (be it a Postgres cluster of 1 or more nodes) to a second space (with less more or more nodes), and having to manipulate clusters with ALTER SEQUENCE commands to ensure that there is no overlap in value does not help much to avoid support at 3AM in case of sudden value conflicts because an application has gone wild, especially if the node fleet needs to be elastic and flexible (yep, there's also that). Note that there are also limits with generated columns that feed from the in-core sequence computation of Postgres where all the sequence data is stored in a pseudo-heap table, relying on buffer locks to make sure that in-place updates are concurrent safe. So this thread is about extending the set of possibilities in this area for application developers to control how sequences are computed. First here is a summary of the use cases that have been mentioned where a custom computation is handy, based on properties that I've understood from the conversation: - Control of computation of values on a node and cluster-basis, usually coming with three properties (put a snowflake ID here): -- Global component, usually put in the first bits to force an ordering of the values across all the nodes. For snowflakes, this is covered by a timestamp, to which an offset can be applied. -- Local component, where a portion of the value bits are decided depending on the node where the value is computed. -- Local incrementation, where the last bits in the value are used to loop if the two first ones happen to be equal, to ensure uniqueness. - Cache range of values at node level or session level, retrieved from a unique source shared by multiple nodes. The range of values is retrieved from a single source (PostgreSQL node itself), cached in a shared pool in a node or just a backend context for consumption by a session. - Transactional behavior to minimize value gaps, which is something I have mentioned but I'm a bit meh on this property as value uniqueness is key, while users have learnt to live with value gaps. Still the APIs can make that possible if autovacuum is able to understand that some clean up needs to happen. Another set of things that have been mentioned: - Is it even correct to call this concept an access method? Should a different keyword be used? This depends on the stack layer where the callbacks associated to a sequence are added, I assume. Still, based on the infrastructure that we already have in place for tables and indexes (commands, GUCs), this is still kind of the correct concept to me because we can rely on a lot of existing infrastructure, but I also get that depending on one's view the opinion diverges. - More pluggable layers. The final picture will most likely involve multiple layers of APIs, and not only what's proposed here, with xpoints mentioned about: -- Custom data types. My answer on this one is that this will need to be controlled by a different clause. I think that this is a different feature than the "access method" approach proposed here that would need to happen on top of what's here, where the point is to control the computation (and anything I've seen lately would unlock up to 64b of computation space hidden behind integer-like data types). Other cluster products out there have also a concept of user-related data types, which have to be integer-like. -- Custom nextval() functions. Here we are going to need a split between the in-core portion of sequences related to system catalogs and the facilities that can be accessed once a sequence OID is known. The patch proposed plugs into nextval_internal() for two reasons: being able to let CACHE be handled by the core code and not the AM, and easier support for generated columns with the existing types where nextval_internal() is called from the executor. This part, also, is going to require a new SQL clause. Perhaps something will happen at some point in the SQL specification itself to put some guidelines, who knows. While on it, I have noticed a couple of conflicts while rebasing, so attached is a refreshed patch set. Thanks, -- Michael Attachments: [text/x-diff] v6-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patch (5.8K, ../../[email protected]/2-v6-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patch) download | inline diff: From 63112231dd41af6898b16d8e52de44b120ed89c7 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Tue, 27 Feb 2024 09:02:57 +0900 Subject: [PATCH v6 1/8] Switch pg_sequence_last_value() to report a tuple and 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 | 24 ++++++++++++++++++------ src/bin/pg_dump/pg_dump.c | 16 +++++++++++++--- src/test/regress/expected/rules.out | 7 ++++++- 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 6a5476d3c4..d7f17878c4 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -3325,9 +3325,11 @@ proargmodes => '{i,o,o,o,o,o,o,o}', proargnames => '{sequence_oid,start_value,minimum_value,maximum_value,increment,cycle_option,cache_size,data_type}', prosrc => 'pg_sequence_parameters' }, -{ oid => '4032', descr => 'sequence last value', +{ oid => '4032', descr => 'sequence last value data', proname => 'pg_sequence_last_value', provolatile => 'v', proparallel => 'u', - prorettype => 'int8', proargtypes => 'regclass', + prorettype => 'record', proargtypes => 'regclass', + proallargtypes => '{regclass,bool,int8}', proargmodes => '{i,o,o}', + proargnames => '{seqid,is_called,last_value}', prosrc => 'pg_sequence_last_value' }, { oid => '275', descr => 'return the next oid for a system table', diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index efb29adeb3..c52fe09501 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 = S.seqrelid) diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 28f8522264..73d79a7a42 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -1774,11 +1774,19 @@ pg_sequence_parameters(PG_FUNCTION_ARGS) Datum pg_sequence_last_value(PG_FUNCTION_ARGS) { +#define PG_SEQUENCE_LAST_VALUE_COLS 2 Oid relid = PG_GETARG_OID(0); + Datum values[PG_SEQUENCE_LAST_VALUE_COLS] = {0}; + bool nulls[PG_SEQUENCE_LAST_VALUE_COLS] = {0}; SeqTable elm; Relation seqrel; bool is_called = false; - int64 result = 0; + int64 last_value = 0; + TupleDesc tupdesc; + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); /* open and lock sequence */ init_sequence(relid, &elm, &seqrel); @@ -1807,16 +1815,20 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) seq = read_seq_tuple(seqrel, &buf, &seqtuple); is_called = seq->is_called; - result = seq->last_value; + last_value = seq->last_value; UnlockReleaseBuffer(buf); } + else + { + nulls[0] = true; + nulls[1] = true; + } sequence_close(seqrel, NoLock); - if (is_called) - PG_RETURN_INT64(result); - else - PG_RETURN_NULL(); + values[0] = BoolGetDatum(is_called); + values[1] = Int64GetDatum(last_value); + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index e324070828..4b1674c5df 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -17562,9 +17562,19 @@ dumpSequenceData(Archive *fout, const TableDataInfo *tdinfo) bool called; PQExpBuffer query = createPQExpBuffer(); - 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 >= 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)); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 13178e2b3d..9541f4cfc0 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 -- 2.45.1 [text/x-diff] v6-0002-Remove-FormData_pg_sequence_data-from-init_params.patch (9.2K, ../../[email protected]/3-v6-0002-Remove-FormData_pg_sequence_data-from-init_params.patch) download | inline diff: From d542e84cd4705170785dce99c6dbbcc8ce095293 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:00:45 +0900 Subject: [PATCH v6 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/sequence.c index 73d79a7a42..5bc7a74d5f 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 rel, 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); 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 = makeNode(CreateStmt); @@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) /* 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); /* @@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) { case SEQ_COL_LASTVAL: coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(seqdataform.last_value); + value[i - 1] = Int64GetDatumFast(last_value); break; case SEQ_COL_LOG: coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); @@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) ObjectAddress address; Relation rel; HeapTuple seqtuple; + bool reset_state = false; + bool is_called; + int64 last_value; HeapTuple newdatatuple; /* 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 = heap_copytuple(&datatuple); newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); + last_value = newdataform->last_value; + is_called = newdataform->is_called; UnlockReleaseBuffer(buf); /* 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); /* 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 = last_value; + newdataform->is_called = is_called; + if (reset_state) + newdataform->log_cnt = 0; fill_seq_with_data(seqrel, newdatatuple); } @@ -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). Also + * 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 overridden. * * Note: we force a sequence rewrite whenever we change parameters that affect - * 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. Currently, * 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, } /* - * 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 = 0; + *reset_state = true; /* AS type */ if (as_type != NULL) @@ -1406,7 +1423,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("INCREMENT must not be zero"))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1418,7 +1435,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, { seqform->seqcycle = boolVal(is_cycled->arg); Assert(BoolIsValid(seqform->seqcycle)); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1429,7 +1446,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (max_value != NULL && max_value->arg) { seqform->seqmax = defGetInt64(max_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || max_value != NULL || reset_max_value) { @@ -1445,7 +1462,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmax = -1; /* descending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate maximum value. No need to check INT8 as seqmax is an int64 */ @@ -1461,7 +1478,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (min_value != NULL && min_value->arg) { seqform->seqmin = defGetInt64(min_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || min_value != NULL || reset_min_value) { @@ -1477,7 +1494,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmin = 1; /* ascending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* 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 != NULL) { if (restart_value->arg != NULL) - seqdataform->last_value = defGetInt64(restart_value); + *last_value = defGetInt64(restart_value); else - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; - seqdataform->log_cnt = 0; + *last_value = seqform->seqstart; + *is_called = false; + *reset_state = true; } else if (isInit) { - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; + *last_value = seqform->seqstart; + *is_called = false; } /* 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))); /* CACHE */ @@ -1563,7 +1580,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("CACHE (%lld) must be greater than zero", (long long) seqform->seqcache))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { -- 2.45.1 [text/x-diff] v6-0003-Integrate-addition-of-attributes-for-sequences-wi.patch (11.1K, ../../[email protected]/4-v6-0003-Integrate-addition-of-attributes-for-sequences-wi.patch) download | inline diff: From d5cc3a8c1dd9c980d3bbbdfc24ba9c0ea5fc03c6 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 19 Jan 2024 15:00:14 +0900 Subject: [PATCH v6 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 85a62b538e..6823f595ae 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2351,6 +2351,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/sequence.c index 5bc7a74d5f..33832d9fa8 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 = NIL; + List *atcmds = 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 = NIL; for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) { ColumnDef *coldef = NULL; @@ -198,7 +200,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) coldef->is_not_null = true; null[i - 1] = false; - stmt->tableElts = lappend(stmt->tableElts, coldef); + elts = lappend(elts, coldef); } stmt->relation = seq->sequence; @@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; + /* + * Initial relation has no attributes, these are added later. + */ + stmt->tableElts = NIL; address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + tupDesc = RelationGetDescr(rel); /* now initialize the sequence's data */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 66cda26a25..4d42ee442b 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4491,6 +4491,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 */ @@ -4802,6 +4803,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* Recursion occurs during execution phase */ pass = 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 = 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, @@ -5227,6 +5235,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 = ATExecAddColumn(wqueue, tab, rel, &cmd, cmd->recurse, false, @@ -6404,6 +6413,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; case T_CreateSeqStmt: + EventTriggerAlterTableStart(parsetree); address = DefineSequence(pstate, (CreateSeqStmt *) parsetree); + /* stashed internally */ + commandCollected = true; + EventTriggerAlterTableEnd(); break; case T_AlterSeqStmt: diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/test/modules/test_ddl_deparse/expected/alter_table.out index 6daa186a84..24b2179455 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 of sequence parent_b_seq NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type ADD COLUMN (and recurse) desc column b of table parent NOTICE: DDL test: type simple, tag ALTER SEQUENCE @@ -76,7 +79,10 @@ NOTICE: subcommand: type SET NOT NULL desc column a of table parent NOTICE: subcommand: type SET NOT NULL desc column a of table child NOTICE: subcommand: type SET NOT NULL desc column a of table grandchild 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 of 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 table parent diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out 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 of 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 2178ce83e9..2be95f99f8 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 of 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 of 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 of sequence datatype_table_is_small_seq NOTICE: DDL test: type simple, tag CREATE TABLE NOTICE: DDL test: type simple, tag CREATE INDEX NOTICE: DDL test: type simple, tag CREATE INDEX diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c index 67ff2b6367..61236a6343 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 = "ADD COLUMN"; break; + case AT_AddColumnToSequence: + strtype = "ADD COLUMN TO SEQUENCE"; + break; case AT_AddColumnToView: strtype = "ADD COLUMN TO VIEW"; break; -- 2.45.1 [text/x-diff] v6-0004-Refactor-code-for-in-core-local-sequences.patch (54.0K, ../../[email protected]/5-v6-0004-Refactor-code-for-in-core-local-sequences.patch) download | inline diff: From 8550949aa40ab2613a93a95457240cc38e3d922c Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Thu, 25 Apr 2024 16:49:47 +0900 Subject: [PATCH v6 4/8] Refactor code for in-core "local" sequences This commit restructures the code of in-core sequences into a new set of files: - seqdesc.c is renamed to seqlocaldesc.c. - seqlocalxlog.c to the code in the WAL replay logic. - seqlocalam.c to store a set of routines called from sequence.c, finishing the separation between the main sequence logic and the in-core sequences. - seqlocalam.h to store the AM-specific structures and routine related to the in-core sequences. WAL records are renamed to "SequenceLocal" with structures, variables and file structures mapping to that. --- src/include/access/rmgrlist.h | 2 +- src/include/access/seqlocalam.h | 58 ++ src/backend/access/rmgrdesc/Makefile | 2 +- src/backend/access/rmgrdesc/meson.build | 2 +- .../rmgrdesc/{seqdesc.c => seqlocaldesc.c} | 20 +- src/backend/access/sequence/Makefile | 2 +- src/backend/access/sequence/meson.build | 2 + src/backend/access/sequence/seqlocalam.c | 634 ++++++++++++++++++ src/backend/access/sequence/seqlocalxlog.c | 82 +++ src/backend/access/transam/rmgr.c | 1 + src/backend/commands/sequence.c | 621 +---------------- src/bin/pg_waldump/.gitignore | 2 +- src/bin/pg_waldump/rmgrdesc.c | 1 + src/bin/pg_waldump/t/001_basic.pl | 2 +- 14 files changed, 817 insertions(+), 614 deletions(-) create mode 100644 src/include/access/seqlocalam.h rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%) create mode 100644 src/backend/access/sequence/seqlocalam.c create mode 100644 src/backend/access/sequence/seqlocalxlog.c diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h index 78e6b908c6..fa94beb6ed 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, btree_identify, btree_xlog PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL, hash_mask, NULL) PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, 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, NULL, seq_mask, NULL) +PG_RMGR(RM_SEQ_LOCAL_ID, "SequenceLocal", seq_local_redo, seq_local_desc, seq_local_identify, NULL, NULL, seq_local_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, NULL, brin_mask, NULL) PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL, NULL, NULL) diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h new file mode 100644 index 0000000000..225fb9a2cb --- /dev/null +++ b/src/include/access/seqlocalam.h @@ -0,0 +1,58 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.h + * Local sequence access method. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/access/seqlocalam.h + * + *------------------------------------------------------------------------- + */ +#ifndef SEQLOCALAM_H +#define SEQLOCALAM_H + +#include "access/xlogreader.h" +#include "storage/relfilelocator.h" +#include "utils/rel.h" + +/* XLOG stuff */ +#define XLOG_SEQ_LOCAL_LOG 0x00 + +typedef struct xl_seq_local_rec +{ + RelFileLocator locator; + /* SEQUENCE TUPLE DATA FOLLOWS AT THE END */ +} xl_seq_local_rec; + +/* + * The "special area" of a local sequence's buffer page looks like this. + */ +#define SEQ_LOCAL_MAGIC 0x1717 + +typedef struct seq_local_magic +{ + uint32 magic; +} seq_local_magic; + +extern void seq_local_redo(XLogReaderState *record); +extern void seq_local_desc(StringInfo buf, XLogReaderState *record); +extern const char *seq_local_identify(uint8 info); +extern void seq_local_mask(char *page, BlockNumber blkno); + +/* access routines */ +extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last); +extern const char *seq_local_get_table_am(void); +extern void seq_local_init(Relation rel, int64 last_value, bool is_called); +extern void seq_local_setval(Relation rel, int64 next, bool iscalled); +extern void seq_local_reset(Relation rel, int64 startv, bool is_called, + bool reset_state); +extern void seq_local_get_state(Relation rel, int64 *last_value, + bool *is_called); +extern void seq_local_change_persistence(Relation rel, + char newrelpersistence); + +#endif /* SEQLOCALAM_H */ diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile index cd95eec37f..e5900ed77a 100644 --- a/src/backend/access/rmgrdesc/Makefile +++ b/src/backend/access/rmgrdesc/Makefile @@ -24,7 +24,7 @@ OBJS = \ relmapdesc.o \ replorigindesc.o \ rmgrdesc_utils.o \ - seqdesc.o \ + seqlocaldesc.o \ smgrdesc.o \ spgdesc.o \ standbydesc.o \ diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build index e8b7a65fc7..e390d385bf 100644 --- a/src/backend/access/rmgrdesc/meson.build +++ b/src/backend/access/rmgrdesc/meson.build @@ -17,7 +17,7 @@ rmgr_desc_sources = files( 'relmapdesc.c', 'replorigindesc.c', 'rmgrdesc_utils.c', - 'seqdesc.c', + 'seqlocaldesc.c', 'smgrdesc.c', 'spgdesc.c', 'standbydesc.c', diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/seqlocaldesc.c similarity index 64% rename from src/backend/access/rmgrdesc/seqdesc.c rename to src/backend/access/rmgrdesc/seqlocaldesc.c index cf0e02ded5..409c67335e 100644 --- a/src/backend/access/rmgrdesc/seqdesc.c +++ b/src/backend/access/rmgrdesc/seqlocaldesc.c @@ -1,44 +1,44 @@ /*------------------------------------------------------------------------- * - * seqdesc.c - * rmgr descriptor routines for commands/sequence.c + * seqlocaldesc.c + * rmgr descriptor routines for sequence/seqlocal.c * * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * src/backend/access/rmgrdesc/seqdesc.c + * src/backend/access/rmgrdesc/seqlocaldesc.c * *------------------------------------------------------------------------- */ #include "postgres.h" -#include "commands/sequence.h" +#include "access/seqlocalam.h" void -seq_desc(StringInfo buf, XLogReaderState *record) +seq_local_desc(StringInfo buf, XLogReaderState *record) { char *rec = XLogRecGetData(record); uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - xl_seq_rec *xlrec = (xl_seq_rec *) rec; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) rec; - if (info == XLOG_SEQ_LOG) + if (info == XLOG_SEQ_LOCAL_LOG) appendStringInfo(buf, "rel %u/%u/%u", xlrec->locator.spcOid, xlrec->locator.dbOid, xlrec->locator.relNumber); } const char * -seq_identify(uint8 info) +seq_local_identify(uint8 info) { const char *id = NULL; switch (info & ~XLR_INFO_MASK) { - case XLOG_SEQ_LOG: - id = "LOG"; + case XLOG_SEQ_LOCAL_LOG: + id = "SEQ_LOCAL_LOG"; break; } diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile index 9f9d31f542..a15ceec1c0 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index 1843aed38d..4d24f900b6 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -1,5 +1,7 @@ # Copyright (c) 2022-2024, PostgreSQL Global Development Group backend_sources += files( + 'seqlocalam.c', + 'seqlocalxlog.c', 'sequence.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c new file mode 100644 index 0000000000..b0a0d72966 --- /dev/null +++ b/src/backend/access/sequence/seqlocalam.c @@ -0,0 +1,634 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.c + * Local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalam.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/multixact.h" +#include "access/seqlocalam.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 SEQ_LOCAL_LOG_VALS 32 + +/* Format of tuples stored in heap table associated to local sequences */ +typedef struct FormData_pg_seq_local_data +{ + int64 last_value; + int64 log_cnt; + bool is_called; +} FormData_pg_seq_local_data; + +typedef FormData_pg_seq_local_data *Form_pg_seq_local_data; + +/* + * Columns of a local sequence relation + */ +#define SEQ_LOCAL_COL_LASTVAL 1 +#define SEQ_LOCAL_COL_LOG 2 +#define SEQ_LOCAL_COL_CALLED 3 + +#define SEQ_LOCAL_COL_FIRSTCOL SEQ_LOCAL_COL_LASTVAL +#define SEQ_LOCAL_COL_LASTCOL SEQ_LOCAL_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_LOCAL_LOG_VALS 32 + +static Form_pg_seq_local_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 tuple + * + * *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_seq_local_data +read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + seq_local_magic *sm; + Form_pg_seq_local_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SEQ_LOCAL_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_pg_seq_local_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 == RELPERSISTENCE_UNLOGGED) + { + SMgrRelation srel; + + srel = 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 content + */ +static void +fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + seq_local_magic *sm; + OffsetNumber offnum; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + sm->magic = SEQ_LOCAL_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) tuple->t_data, tuple->t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_nextval() + * + * Allocate a new value for a local sequence, based on the sequence + * configuration. + */ +int64 +seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + int64 result; + int64 fetch; + int64 next; + int64 rescnt = 0; + int64 log; + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + Page page; + bool logit = false; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + page = BufferGetPage(buf); + + *last = next = result = seq->last_value; + fetch = cache; + log = 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_LOCAL_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 = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = true; + } + else + { + XLogRecPtr redoptr = GetRedoRecPtr(); + + if (PageGetLSN(page) <= redoptr) + { + /* last update of seq was before checkpoint */ + fetch = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = 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 >= 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 = minv; + } + else + next += incby; + } + else + { + /* descending sequence */ + if ((minv < 0 && next < minv - incby) || + (minv >= 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 = maxv; + } + else + next += incby; + } + fetch--; + if (rescnt < cache) + { + log--; + rescnt++; + *last = next; + if (rescnt == 1) /* if it's first result - */ + result = next; /* it's what to return */ + } + } + + log -= fetch; /* adjust for any unfetched numbers */ + Assert(log >= 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_seq_local_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 = next; + seq->is_called = true; + seq->log_cnt = 0; + + xlrec.locator = rel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + /* Now update sequence tuple to the intended final state */ + seq->last_value = *last; /* last fetched number */ + seq->is_called = true; + seq->log_cnt = log; /* how much is logged */ + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + return result; +} + +/* + * seq_local_get_table_am() + * + * Return the table access method used by this sequence. + */ +const char * +seq_local_get_table_am(void) +{ + return "heap"; +} + +/* + * seq_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 + * the information guessed from pg_sequences. This is the first tuple + * inserted after the relation has been created, filling in its heap + * table. + */ +void +seq_local_init(Relation rel, int64 last_value, bool is_called) +{ + Datum value[SEQ_LOCAL_COL_LASTCOL]; + bool null[SEQ_LOCAL_COL_LASTCOL]; + List *elts = NIL; + List *atcmds = NIL; + ListCell *lc; + TupleDesc tupdesc; + HeapTuple tuple; + + /* + * Create relation (and fill value[] and null[] for the initial tuple). + */ + for (int i = SEQ_LOCAL_COL_FIRSTCOL; i <= SEQ_LOCAL_COL_LASTCOL; i++) + { + ColumnDef *coldef = NULL; + + switch (i) + { + case SEQ_LOCAL_COL_LASTVAL: + coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatumFast(last_value); + break; + case SEQ_LOCAL_COL_LOG: + coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatum(0); + break; + case SEQ_LOCAL_COL_CALLED: + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + value[i - 1] = BoolGetDatum(is_called); + break; + } + + coldef->is_not_null = true; + null[i - 1] = false; + elts = lappend(elts, coldef); + } + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, value, null); + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_setval() + * + * Callback for setval(). + */ +void +seq_local_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->last_value = next; /* last fetched number */ + seq->is_called = iscalled; + seq->log_cnt = 0; + + MarkBufferDirty(buf); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel)) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + Page page = BufferGetPage(buf); + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_reset() + * + * Perform a hard reset on the local sequence, rewriting its heap data + * entirely. + */ +void +seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) +{ + Form_pg_seq_local_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 = 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 = (Form_pg_seq_local_data) GETSTRUCT(tuple); + seq->last_value = startv; + seq->is_called = is_called; + if (reset_state) + seq->log_cnt = 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 == InvalidTransactionId); + Assert(rel->rd_rel->relminmxid == InvalidMultiXactId); + + /* + * Insert the modified tuple into the new storage file. + */ + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_get_state() + * + * Retrieve the state of a local sequence. + */ +void +seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + *last_value = seq->last_value; + *is_called = seq->is_called; + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_change_persistence() + * + * Persistence change for the local sequence Relation. + */ +void +seq_local_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/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c new file mode 100644 index 0000000000..db0ad969db --- /dev/null +++ b/src/backend/access/sequence/seqlocalxlog.c @@ -0,0 +1,82 @@ +/*------------------------------------------------------------------------- + * + * seqlocalxlog.c + * WAL replay logic for local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalxlog.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/bufmask.h" +#include "access/seqlocalam.h" +#include "access/xlogutils.h" +#include "storage/block.h" + +/* + * Mask a Sequence page before performing consistency checks on it. + */ +void +seq_local_mask(char *page, BlockNumber blkno) +{ + mask_page_lsn_and_checksum(page); + + mask_unused_space(page); +} + +void +seq_local_redo(XLogReaderState *record) +{ + XLogRecPtr lsn = record->EndRecPtr; + uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; + Buffer buffer; + Page page; + Page localpage; + char *item; + Size itemsz; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record); + seq_local_magic *sm; + + if (info != XLOG_SEQ_LOCAL_LOG) + elog(PANIC, "seq_redo: unknown op code %u", info); + + buffer = XLogInitBufferForRedo(record, 0); + page = (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 = (Page) palloc(BufferGetPageSize(buffer)); + + PageInit(localpage, BufferGetPageSize(buffer), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(localpage); + sm->magic = SEQ_LOCAL_MAGIC; + + item = (char *) xlrec + sizeof(xl_seq_local_rec); + itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec); + + if (PageAddItem(localpage, (Item) item, itemsz, + FirstOffsetNumber, false, false) == InvalidOffsetNumber) + elog(PANIC, "seq_local_redo: failed to add item to page"); + + PageSetLSN(localpage, lsn); + + memcpy(page, localpage, BufferGetPageSize(buffer)); + MarkBufferDirty(buffer); + UnlockReleaseBuffer(buffer); + + pfree(localpage); +} diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c index 1b7499726e..cc92268937 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/seqlocalam.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/sequence.c index 33832d9fa8..2ba572958d 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,6 +16,7 @@ #include "access/bufmask.h" #include "access/htup_details.h" +#include "access/seqlocalam.h" #include "access/multixact.h" #include "access/relation.h" #include "access/sequence.h" @@ -50,23 +51,6 @@ #include "utils/varlena.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 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 current * session. This is needed to hold onto nextval/currval state. (We can't @@ -96,13 +80,9 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */ */ static SeqTableData *last_used_seq = NULL; -static void fill_seq_with_data(Relation rel, HeapTuple tuple); -static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber 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_identity, 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 = NIL; - List *atcmds = NIL; - ListCell *lc; Datum pgs_values[Natts_pg_sequence]; bool pgs_nulls[Natts_pg_sequence]; - int i; /* * 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); - /* - * Create relation (and fill value[] and null[] for the tuple) - */ - for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) - { - ColumnDef *coldef = NULL; - - switch (i) - { - case SEQ_COL_LASTVAL: - coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(last_value); - break; - case SEQ_COL_LOG: - coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatum((int64) 0); - break; - case SEQ_COL_CALLED: - coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); - value[i - 1] = BoolGetDatum(false); - break; - } - - coldef->is_not_null = true; - null[i - 1] = false; - - elts = lappend(elts, coldef); - } - stmt->relation = seq->sequence; stmt->inhRelations = NIL; stmt->constraints = NIL; @@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) */ stmt->tableElts = NIL; + /* + * Initial relation has no attributes, these can be added later via the + * "init" AM callback. + */ + stmt->tableElts = NIL; + address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); - /* Add all the attributes to the sequence */ - foreach(lc, elts) - { - AlterTableCmd *atcmd; - - atcmd = makeNode(AlterTableCmd); - atcmd->subtype = AT_AddColumnToSequence; - atcmd->def = (Node *) lfirst(lc); - atcmds = lappend(atcmds, atcmd); - } - - /* - * No recursion needed. Note that EventTriggerAlterTableStart() should - * have been called. - */ - AlterTableInternal(RelationGetRelid(rel), atcmds, false); - CommandCounterIncrement(); - - tupDesc = RelationGetDescr(rel); - - /* now initialize the sequence's data */ - tuple = heap_form_tuple(tupDesc, value, null); - fill_seq_with_data(rel, tuple); + /* now initialize the sequence table structure and its data */ + seq_local_init(rel, last_value, is_called); /* 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); pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid)); if (!HeapTupleIsValid(pgstuple)) @@ -315,40 +240,8 @@ ResetSequence(Oid seq_relid) startv = pgsform->seqstart; ReleaseSysCache(pgstuple); - /* - * Copy the existing sequence tuple. - */ - tuple = 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 = (Form_pg_sequence_data) GETSTRUCT(tuple); - seq->last_value = startv; - seq->is_called = false; - seq->log_cnt = 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 == InvalidTransactionId); - Assert(seq_rel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - fill_seq_with_data(seq_rel, tuple); + /* Sequence state is forcibly reset here. */ + seq_local_reset(seq_rel, startv, false, true); /* 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); } -/* - * 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 == RELPERSISTENCE_UNLOGGED) - { - SMgrRelation srel; - - srel = 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 content - */ -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 = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, - EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); - Assert(BufferGetBlockNumber(buf) == 0); - - page = BufferGetPage(buf); - - PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(page); - sm->magic = SEQ_MAGIC; - - /* Now insert sequence tuple */ - - /* - * Since VACUUM does not process sequences, we have to force the tuple to - * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, - InvalidOffsetNumber, false, false); - if (offnum != FirstOffsetNumber) - elog(ERROR, "failed to add sequence tuple to page"); - - /* XLOG stuff */ - if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = rel->rd_locator; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) tuple->t_data, tuple->t_len); - - recptr = 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 = false; bool is_called; int64 last_value; - HeapTuple newdatatuple; /* Open and lock sequence, and check for ownership along the way. */ relid = RangeVarGetRelidExtended(stmt->sequence, @@ -507,16 +296,8 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) seqform = (Form_pg_sequence) GETSTRUCT(seqtuple); - /* 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 = heap_copytuple(&datatuple); - newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); - last_value = newdataform->last_value; - is_called = newdataform->is_called; - - UnlockReleaseBuffer(buf); + /* Read sequence data */ + seq_local_get_state(seqrel, &last_value, &is_called); /* 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(); - /* - * 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 == InvalidTransactionId); - Assert(seqrel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - newdataform->last_value = last_value; - newdataform->is_called = is_called; - if (reset_state) - newdataform->log_cnt = 0; - fill_seq_with_data(seqrel, newdatatuple); + seq_local_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) { SeqTable elm; Relation seqrel; - Buffer buf; - HeapTupleData seqdatatuple; init_sequence(relid, &elm, &seqrel); @@ -589,10 +346,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - (void) read_seq_tuple(seqrel, &buf, &seqdatatuple); - RelationSetNewRelfilenumber(seqrel, newrelpersistence); - fill_seq_with_data(seqrel, &seqdatatuple); - UnlockReleaseBuffer(buf); + seq_local_change_persistence(seqrel, newrelpersistence); 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 = 0; + int64 result; bool cycle; - bool logit = false; /* open and lock sequence */ init_sequence(relid, &elm, &seqrel); @@ -717,105 +462,9 @@ nextval_internal(Oid relid, bool check_permissions) cycle = pgsform->seqcycle; ReleaseSysCache(pgstuple); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - page = BufferGetPage(buf); - - last = next = result = seq->last_value; - fetch = cache; - log = 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 = log = fetch + SEQ_LOG_VALS; - logit = true; - } - else - { - XLogRecPtr redoptr = GetRedoRecPtr(); - - if (PageGetLSN(page) <= redoptr) - { - /* last update of seq was before checkpoint */ - fetch = log = fetch + SEQ_LOG_VALS; - logit = 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 >= 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 = minv; - } - else - next += incby; - } - else - { - /* descending sequence */ - if ((minv < 0 && next < minv - incby) || - (minv >= 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 = maxv; - } - else - next += incby; - } - fetch--; - if (rescnt < cache) - { - log--; - rescnt++; - last = next; - if (rescnt == 1) /* if it's first result - */ - result = next; /* it's what to return */ - } - } - - log -= fetch; /* adjust for any unfetched numbers */ - Assert(log >= 0); + /* retrieve next value from the access method */ + result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -825,69 +474,6 @@ nextval_internal(Oid relid, bool check_permissions) last_used_seq = elm; - /* - * 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 = next; - seq->is_called = true; - seq->log_cnt = 0; - - xlrec.locator = seqrel->rd_locator; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - /* Now update sequence tuple to the intended final state */ - seq->last_value = last; /* last fetched number */ - seq->is_called = true; - seq->log_cnt = log; /* how much is logged */ - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); - sequence_close(seqrel, NoLock); 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()"); - /* lock page buffer and read tuple */ - seq = 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(); - /* ready to change the on-disk (or really, in-buffer) tuple */ - START_CRIT_SECTION(); - - seq->last_value = next; /* last fetched number */ - seq->is_called = iscalled; - seq->log_cnt = 0; - - MarkBufferDirty(buf); - - /* XLOG stuff */ - if (RelationNeedsWAL(seqrel)) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - Page page = BufferGetPage(buf); - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = seqrel->rd_locator; - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); + /* Call the access method callback */ + seq_local_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1208,62 +759,6 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) } -/* - * Given an opened sequence relation, lock the page buffer and find the tuple - * - * *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 = ReadBuffer(rel, 0); - LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); - - page = BufferGetPage(*buf); - sm = (sequence_magic *) PageGetSpecialPointer(page); - - if (sm->magic != SEQ_MAGIC) - elog(ERROR, "bad magic number in sequence \"%s\": %08X", - RelationGetRelationName(rel), sm->magic); - - lp = PageGetItemId(page, FirstOffsetNumber); - Assert(ItemIdIsNormal(lp)); - - /* Note we currently only bother to set these two fields of *seqdatatuple */ - seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); - seqdatatuple->t_len = 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) != InvalidTransactionId) - { - HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); - seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; - seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; - MarkBufferDirtyHint(*buf, true); - } - - seq = (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 @@ -1850,22 +1345,14 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) if (!RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; - - seq = read_seq_tuple(seqrel, &buf, &seqtuple); - - is_called = seq->is_called; - last_value = seq->last_value; - - UnlockReleaseBuffer(buf); + seq_local_get_state(seqrel, &last_value, &is_called); } else { nulls[0] = true; nulls[1] = true; } + sequence_close(seqrel, NoLock); values[0] = BoolGetDatum(is_called); @@ -1873,57 +1360,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } - -void -seq_redo(XLogReaderState *record) -{ - XLogRecPtr lsn = record->EndRecPtr; - uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - Buffer buffer; - Page page; - Page localpage; - char *item; - Size itemsz; - xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record); - sequence_magic *sm; - - if (info != XLOG_SEQ_LOG) - elog(PANIC, "seq_redo: unknown op code %u", info); - - buffer = XLogInitBufferForRedo(record, 0); - page = (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 = (Page) palloc(BufferGetPageSize(buffer)); - - PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(localpage); - sm->magic = SEQ_MAGIC; - - item = (char *) xlrec + sizeof(xl_seq_rec); - itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec); - - if (PageAddItem(localpage, (Item) item, itemsz, - FirstOffsetNumber, false, false) == 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. */ @@ -1938,14 +1374,3 @@ ResetSequenceCaches(void) last_used_seq = 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..8d1195de26 100644 --- a/src/bin/pg_waldump/.gitignore +++ b/src/bin/pg_waldump/.gitignore @@ -10,13 +10,13 @@ /gistdesc.c /hashdesc.c /heapdesc.c +/seqlocaldesc.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..2fccc7a4e2 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/seqlocalam.h" #include "access/multixact.h" #include "access/nbtxlog.h" #include "access/rmgr.h" diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl index 578e473139..0b911048e1 100644 --- a/src/bin/pg_waldump/t/001_basic.pl +++ b/src/bin/pg_waldump/t/001_basic.pl @@ -67,7 +67,7 @@ Btree Hash Gin Gist -Sequence +SequenceLocal SPGist BRIN CommitTs -- 2.45.1 [text/x-diff] v6-0005-Sequence-access-methods-backend-support.patch (60.9K, ../../[email protected]/6-v6-0005-Sequence-access-methods-backend-support.patch) download | inline diff: From 79163a333923e10e8f057d476b6713bf371e6657 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Thu, 25 Apr 2024 17:15:20 +0900 Subject: [PATCH v6 5/8] Sequence access methods - backend support The "seqlocal" sequence AM is now plugged in as a handler in the relcache, and a set of callbacks in sequenceam.h. --- src/include/access/seqlocalam.h | 15 -- src/include/access/sequenceam.h | 181 ++++++++++++++++++ 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/meson.build | 1 + src/backend/access/sequence/seqlocalam.c | 41 +++- 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/psql/describe.c | 2 + src/bin/psql/tab-complete.c | 4 +- src/test/regress/expected/create_am.out | 55 ++++-- src/test/regress/expected/opr_sanity.out | 12 ++ src/test/regress/expected/psql.out | 96 +++++----- src/test/regress/sql/create_am.sql | 24 ++- src/test/regress/sql/opr_sanity.sql | 10 + src/tools/pgindent/typedefs.list | 5 +- 38 files changed, 682 insertions(+), 169 deletions(-) create mode 100644 src/include/access/sequenceam.h create mode 100644 src/backend/access/sequence/sequenceamapi.c diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h index 225fb9a2cb..21936511ac 100644 --- a/src/include/access/seqlocalam.h +++ b/src/include/access/seqlocalam.h @@ -15,7 +15,6 @@ #include "access/xlogreader.h" #include "storage/relfilelocator.h" -#include "utils/rel.h" /* XLOG stuff */ #define XLOG_SEQ_LOCAL_LOG 0x00 @@ -41,18 +40,4 @@ extern void seq_local_desc(StringInfo buf, XLogReaderState *record); extern const char *seq_local_identify(uint8 info); extern void seq_local_mask(char *page, BlockNumber blkno); -/* access routines */ -extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last); -extern const char *seq_local_get_table_am(void); -extern void seq_local_init(Relation rel, int64 last_value, bool is_called); -extern void seq_local_setval(Relation rel, int64 next, bool iscalled); -extern void seq_local_reset(Relation rel, int64 startv, bool is_called, - bool reset_state); -extern void seq_local_get_state(Relation rel, int64 *last_value, - bool *is_called); -extern void seq_local_change_persistence(Relation rel, - char newrelpersistence); - #endif /* SEQLOCALAM_H */ diff --git a/src/include/access/sequenceam.h b/src/include/access/sequenceam.h new file mode 100644 index 0000000000..ac48c8b468 --- /dev/null +++ b/src/include/access/sequenceam.h @@ -0,0 +1,181 @@ +/*------------------------------------------------------------------------- + * + * sequenceam.h + * POSTGRES sequence access method definitions. + * + * + * Portions Copyright (c) 1996-2024, 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 "seqlocal" + +/* 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 value + * allocated. The result returned is the next value of the sequence computed. + */ +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); + +#endif /* SEQUENCEAM_H */ diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat index db87490282..d991a77604 100644 --- a/src/include/catalog/pg_am.dat +++ b/src/include/catalog/pg_am.dat @@ -15,6 +15,9 @@ { oid => '2', oid_symbol => 'HEAP_TABLE_AM_OID', descr => 'heap table access method', amname => 'heap', amhandler => 'heap_tableam_handler', amtype => 't' }, +{ oid => '8048', oid_symbol => 'LOCAL_SEQUENCE_AM_OID', + descr => 'local sequence access method', + amname => 'seqlocal', amhandler => 'seq_local_sequenceam_handler', amtype => 's' }, { oid => '403', oid_symbol => 'BTREE_AM_OID', descr => 'b-tree index access method', amname => 'btree', amhandler => 'bthandler', amtype => '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 */ #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) == RELKIND_TOASTVALUE || \ (relkind) == RELKIND_MATVIEW) +/* + * Relation kinds with a sequence access method (rd_sequenceam). + */ +#define RELKIND_HAS_SEQUENCE_AM(relkind) \ + ((relkind) == RELKIND_SEQUENCE) + extern int errdetail_relkind_not_supported(char relkind); #endif /* EXPOSE_TO_CLIENT_CODE */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index d7f17878c4..17890547c3 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -913,6 +913,12 @@ prorettype => 'table_am_handler', proargtypes => 'internal', prosrc => 'heap_tableam_handler' }, +# Sequence access method handlers +{ oid => '8209', descr => 'local sequence access method handler', + proname => 'seq_local_sequenceam_handler', provolatile => 'v', + prorettype => 'sequence_am_handler', proargtypes => 'internal', + prosrc => 'seq_local_sequenceam_handler' }, + # Index access method handlers { oid => '330', descr => 'btree index access method handler', proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler', @@ -7629,6 +7635,13 @@ { oid => '327', descr => 'I/O', proname => 'index_am_handler_out', prorettype => 'cstring', proargtypes => 'index_am_handler', prosrc => 'index_am_handler_out' }, +{ oid => '8207', descr => 'I/O', + proname => 'sequence_am_handler_in', proisstrict => 'f', + prorettype => 'sequence_am_handler', proargtypes => 'cstring', + prosrc => 'sequence_am_handler_in' }, +{ oid => '8208', descr => 'I/O', + proname => 'sequence_am_handler_out', prorettype => 'cstring', + proargtypes => 'sequence_am_handler', prosrc => 'sequence_am_handler_out' }, { oid => '3311', descr => 'I/O', proname => 'tsm_handler_in', proisstrict => 'f', prorettype => 'tsm_handler', proargtypes => 'cstring', prosrc => 'tsm_handler_in' }, diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat index ceff66ccde..0eba013687 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -627,6 +627,12 @@ typcategory => 'P', typinput => 'index_am_handler_in', typoutput => 'index_am_handler_out', typreceive => '-', typsend => '-', typalign => 'i' }, +{ oid => '8210', + descr => 'pseudo-type for the result of a sequence AM handler function', + typname => 'sequence_am_handler', typlen => '4', typbyval => 't', + typtype => 'p', typcategory => 'P', typinput => 'sequence_am_handler_in', + typoutput => 'sequence_am_handler_out', typreceive => '-', typsend => '-', + typalign => 'i' }, { oid => '3310', descr => 'pseudo-type for the result of a tablesample method function', typname => 'tsm_handler', typlen => '4', typbyval => 't', typtype => 'p', diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 29c511e319..39d2cae422 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -141,6 +141,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); diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.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" -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); -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 = [ '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 6823f595ae..2c18a34c00 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3127,6 +3127,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; 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 **extra, 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 **extra, + 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; + /* + * 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/sequence/Makefile index a15ceec1c0..62006165a1 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = seqlocalam.o seqlocalxlog.o sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o sequenceamapi.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index 4d24f900b6..ea91990552 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -4,4 +4,5 @@ backend_sources += files( 'seqlocalam.c', 'seqlocalxlog.c', 'sequence.c', + 'sequenceamapi.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c index b0a0d72966..2e524c9e99 100644 --- a/src/backend/access/sequence/seqlocalam.c +++ b/src/backend/access/sequence/seqlocalam.c @@ -17,6 +17,7 @@ #include "access/multixact.h" #include "access/seqlocalam.h" +#include "access/sequenceam.h" #include "access/xact.h" #include "access/xloginsert.h" #include "access/xlogutils.h" @@ -24,6 +25,7 @@ #include "commands/tablecmds.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "utils/builtins.h" /* @@ -230,10 +232,10 @@ fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) * Allocate a new value for a local sequence, based on the sequence * configuration. */ -int64 +static int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last) + int64 minv, int64 cache, bool cycle, + int64 *last) { int64 result; int64 fetch; @@ -417,7 +419,7 @@ seq_local_nextval(Relation rel, int64 incby, int64 maxv, * * Return the table access method used by this sequence. */ -const char * +static const char * seq_local_get_table_am(void) { return "heap"; @@ -432,7 +434,7 @@ seq_local_get_table_am(void) * inserted after the relation has been created, filling in its heap * table. */ -void +static void seq_local_init(Relation rel, int64 last_value, bool is_called) { Datum value[SEQ_LOCAL_COL_LASTCOL]; @@ -499,7 +501,7 @@ seq_local_init(Relation rel, int64 last_value, bool is_called) * * Callback for setval(). */ -void +static void seq_local_setval(Relation rel, int64 next, bool iscalled) { Buffer buf; @@ -547,7 +549,7 @@ seq_local_setval(Relation rel, int64 next, bool iscalled) * Perform a hard reset on the local sequence, rewriting its heap data * entirely. */ -void +static void seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) { Form_pg_seq_local_data seq; @@ -600,7 +602,7 @@ seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) * * Retrieve the state of a local sequence. */ -void +static void seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) { Buffer buf; @@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) * * Persistence change for the local sequence Relation. */ -void +static void seq_local_change_persistence(Relation rel, char newrelpersistence) { Buffer buf; @@ -632,3 +634,24 @@ seq_local_change_persistence(Relation rel, char newrelpersistence) fill_seq_with_data(rel, &seqdatatuple); UnlockReleaseBuffer(buf); } + +/* ------------------------------------------------------------------------ + * Definition of the local sequence access method. + * ------------------------------------------------------------------------ + */ +static const SequenceAmRoutine seq_local_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = seq_local_get_table_am, + .init = seq_local_init, + .nextval = seq_local_nextval, + .setval = seq_local_setval, + .reset = seq_local_reset, + .get_state = seq_local_get_state, + .change_persistence = seq_local_change_persistence +}; + +Datum +seq_local_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&seq_local_methods); +} diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/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 sequences - * (in contrast to other relation types like indexes). + * (in contrast to other relation types like indexes) that are independent + * of individual sequence access methods. * *------------------------------------------------------------------------- */ diff --git a/src/backend/access/sequence/sequenceamapi.c b/src/backend/access/sequence/sequenceamapi.c new file mode 100644 index 0000000000..dd1a60d827 --- /dev/null +++ b/src/backend/access/sequence/sequenceamapi.c @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------- + * + * sequenceamapi.c + * general sequence access method routines + * + * Portions Copyright (c) 1996-2024, 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 = 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 = OidFunctionCall0(amhandler); + routine = (SequenceAmRoutine *) DatumGetPointer(datum); + + if (routine == NULL || !IsA(routine, SequenceAmRoutine)) + elog(ERROR, "sequence access method handler %u did not return a SequenceAmRoutine 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 != NULL); + Assert(routine->init != NULL); + Assert(routine->nextval != NULL); + Assert(routine->setval != NULL); + Assert(routine->reset != NULL); + Assert(routine->get_state != NULL); + Assert(routine->change_persistence != 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 = SearchSysCache1(AMOID, + ObjectIdGetDatum(amoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", amoid); + aform = (Form_pg_am) GETSTRUCT(tuple); + Assert(aform->amtype == AMTYPE_SEQUENCE); + amhandleroid = 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 == '\0') + { + GUC_check_errdetail("%s cannot be empty.", + "default_sequence_access_method"); + return false; + } + + if (strlen(*newval) >= 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 != InvalidOid) + { + if (!OidIsValid(get_sequence_am_oid(*newval, true))) + { + /* + * When source == 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 == 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 a122bbffce..e1308916eb 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 != RELKIND_TOASTVALUE) + if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != 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 @@ #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); } +/* + * 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 = INDEX_AM_HANDLEROID; break; + case AMTYPE_SEQUENCE: + expectedType = SEQUENCE_AM_HANDLEROID; + break; case AMTYPE_TABLE: expectedType = TABLE_AM_HANDLEROID; break; diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 2ba572958d..d085b45632 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,10 +16,10 @@ #include "access/bufmask.h" #include "access/htup_details.h" -#include "access/seqlocalam.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 = NIL; stmt->constraints = NIL; stmt->options = NIL; + stmt->accessMethod = seq->accessMethod ? pstrdup(seq->accessMethod) : NULL; stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; @@ -173,7 +174,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) rel = sequence_open(seqoid, AccessExclusiveLock); /* now initialize the sequence table structure and its data */ - seq_local_init(rel, last_value, is_called); + sequence_init(rel, last_value, is_called); /* process OWNED BY if given */ if (owned_by) @@ -241,7 +242,7 @@ ResetSequence(Oid seq_relid) ReleaseSysCache(pgstuple); /* Sequence state is forcibly reset here. */ - seq_local_reset(seq_rel, startv, false, true); + sequence_reset(seq_rel, startv, false, true); /* 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 = (Form_pg_sequence) GETSTRUCT(seqtuple); /* Read sequence data */ - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); /* 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(); - seq_local_reset(seqrel, last_value, is_called, reset_state); + sequence_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -346,7 +347,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - seq_local_change_persistence(seqrel, newrelpersistence); + sequence_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -463,8 +464,8 @@ nextval_internal(Oid relid, bool check_permissions) ReleaseSysCache(pgstuple); /* retrieve next value from the access method */ - result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, - &last); + result = sequence_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -618,7 +619,7 @@ do_setval(Oid relid, int64 next, bool iscalled) GetTopTransactionId(); /* Call the access method callback */ - seq_local_setval(seqrel, next, iscalled); + sequence_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1345,7 +1346,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) if (!RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); } else { diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 4d42ee442b..f68b38184b 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -22,6 +22,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" @@ -947,14 +948,18 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, } /* - * 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 != NULL) { - Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE); - accessMethodId = get_table_am_oid(stmt->accessMethod, false); + Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE || + RELKIND_HAS_SEQUENCE_AM(relkind)); + if (RELKIND_HAS_SEQUENCE_AM(relkind)) + accessMethodId = get_sequence_am_oid(stmt->accessMethod, false); + else + accessMethodId = get_table_am_oid(stmt->accessMethod, false); } else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE) { @@ -967,6 +972,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId)) accessMethodId = get_table_am_oid(default_table_access_method, false); } + else if (RELKIND_HAS_SEQUENCE_AM(relkind)) + { + accessMethodId = get_sequence_am_oid(default_sequence_access_method, false); + } /* * 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 = \ 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 = 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 = 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 4d582950b7..9d75ec555e 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -391,6 +391,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <str> copy_file_name access_method_clause attr_name table_access_method_clause name cursor_name file_name + sequence_access_method_clause cluster_index_specification %type <list> func_name handler_name qual_Op qual_all_Op subquery_Op @@ -4883,23 +4884,26 @@ RefreshMatViewStmt: CreateSeqStmt: CREATE OptTemp SEQUENCE qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); - $4->relpersistence = $2; n->sequence = $4; n->options = $5; + n->accessMethod = $6; n->ownerId = InvalidOid; n->if_not_exists = false; $$ = (Node *) n; } | CREATE OptTemp SEQUENCE IF_P NOT EXISTS qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); $7->relpersistence = $2; n->sequence = $7; n->options = $8; + n->accessMethod = $9; n->ownerId = InvalidOid; n->if_not_exists = true; $$ = (Node *) n; @@ -4936,6 +4940,11 @@ OptParenthesizedSeqOptList: '(' SeqOptList ')' { $$ = $2; } | /*EMPTY*/ { $$ = NIL; } ; +sequence_access_method_clause: + USING name { $$ = $2; } + | /*EMPTY*/ { $$ = NULL; } + ; + SeqOptList: SeqOptElem { $$ = list_make1($1); } | SeqOptList SeqOptElem { $$ = lappend($1, $2); } ; @@ -5932,6 +5941,7 @@ CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name am_type: INDEX { $$ = AMTYPE_INDEX; } + | SEQUENCE { $$ = AMTYPE_SEQUENCE; } | TABLE { $$ = AMTYPE_TABLE; } ; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index d5c2b2ff0b..052f829d17 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" @@ -464,6 +465,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, seqstmt->sequence->relpersistence = cxt->rel ? cxt->rel->rd_rel->relpersistence : cxt->relation->relpersistence; seqstmt->options = seqoptions; + seqstmt->accessMethod = NULL; /* * 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/pseudotypes.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/relcache.c index 35dbb87ae3..aa210618e1 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" @@ -301,6 +303,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); @@ -1206,8 +1209,7 @@ retry: if (relation->rd_rel->relkind == RELKIND_INDEX || relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) RelationInitIndexAccessInfo(relation); - else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || - relation->rd_rel->relkind == RELKIND_SEQUENCE) + else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)) RelationInitTableAccessMethod(relation); else if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { @@ -1216,6 +1218,8 @@ retry: * inherit. */ } + else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind)) + RelationInitSequenceAccessMethod(relation); else Assert(relation->rd_rel->relam == InvalidOid); @@ -1812,17 +1816,9 @@ RelationInitTableAccessMethod(Relation relation) HeapTuple tuple; Form_pg_am aform; - if (relation->rd_rel->relkind == 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 == InvalidOid); - relation->rd_amhandler = 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. @@ -1853,6 +1849,49 @@ RelationInitTableAccessMethod(Relation relation) InitTableAmRoutine(relation); } +/* + * 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 != InvalidOid); + relation->rd_amhandler = GetSequenceAmRoutineId(relation->rd_rel->relam); + + /* + * Now we can fetch the sequence AM's API struct. + */ + relation->rd_sequenceam = GetSequenceAmRoutine(relation->rd_amhandler); + + /* + * From the sequence AM, set its expected table access method. + */ + tableam_name = sequence_get_table_am(relation); + tableam_oid = get_table_am_oid(tableam_name, false); + + tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(tableam_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", + tableam_oid); + aform = (Form_pg_am) GETSTRUCT(tuple); + tableam_handler = aform->amhandler; + ReleaseSysCache(tuple); + + relation->rd_tableam = GetTableAmRoutine(tableam_handler); +} + /* * formrdesc * @@ -3710,14 +3749,17 @@ RelationBuildLocalRelation(const char *relname, rel->rd_rel->relam = accessmtd; /* - * 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); - if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(relkind)) RelationInitTableAccessMethod(rel); + else if (relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); /* * Okay to insert into the relcache hash table. @@ -4332,13 +4374,21 @@ RelationCacheInitializePhase3(void) /* Reload tableam data if needed */ if (relation->rd_tableam == NULL && - (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE)) + (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))) { RelationInitTableAccessMethod(relation); Assert(relation->rd_tableam != NULL); restart = true; } + else if (relation->rd_sequenceam == NULL && + relation->rd_rel->relkind == RELKIND_SEQUENCE) + { + RelationInitSequenceAccessMethod(relation); + Assert(relation->rd_sequenceam != NULL); + + restart = true; + } /* Release hold on the relation */ RelationDecrementReferenceCount(relation); @@ -6325,8 +6375,10 @@ load_relcache_init_file(bool shared) nailed_rels++; /* Load table AM data */ - if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || rel->rd_rel->relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind)) RelationInitTableAccessMethod(rel); + else if (rel->rd_rel->relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); Assert(rel->rd_index == NULL); Assert(rel->rd_indextuple == NULL); @@ -6338,6 +6390,7 @@ load_relcache_init_file(bool shared) Assert(rel->rd_supportinfo == NULL); Assert(rel->rd_indoption == NULL); Assert(rel->rd_indcollation == NULL); + Assert(rel->rd_sequenceam == NULL); Assert(rel->rd_opcoptions == NULL); } diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 46c258be28..4a181b6294 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" @@ -4118,6 +4119,17 @@ struct config_string ConfigureNamesString[] = check_default_table_access_method, NULL, NULL }, + { + {"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default sequence access method for new sequences."), + 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/utils/misc/postgresql.conf.sample index e0567de219..f9d0cadca1 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -703,6 +703,7 @@ # error #search_path = '"$user", public' # schema names #row_security = on +#default_sequence_access_method = 'seqlocal' #default_table_access_method = 'heap' #default_tablespace = '' # a tablespace name, '' uses the default #default_toast_compression = 'pglz' # 'pglz' or 'lz4' diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index f67bf0b892..46b1126618 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -161,10 +161,12 @@ describeAccessMethods(const char *pattern, bool verbose) "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")); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index d453e224d9..22a29dc4bd 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 <name> AS */ else if (TailMatches("ALTER", "SEQUENCE", MatchAny, "AS")) COMPLETE_WITH_CS("smallint", "integer", "bigint"); @@ -3240,7 +3240,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/expected/create_am.out index 35d4cf1d46..f1ad1c583f 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) PARTITION 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 = 'seqlocal'; 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; @@ -519,18 +517,18 @@ FROM pg_class AS pc LEFT JOIN pg_am AS pa ON (pa.oid = pc.relam) WHERE pc.relname LIKE 'tableam_%_heapx' ORDER BY 3, 1, 2; - relkind | amname | relname ----------+--------+----------------------------- - f | | tableam_fdw_heapx - r | heap2 | tableam_parted_1_heapx - r | heap | tableam_parted_2_heapx - p | | tableam_parted_heapx - S | | tableam_seq_heapx - r | heap2 | tableam_tbl_heapx - r | heap2 | tableam_tblas_heapx - m | heap2 | tableam_tblmv_heapx - r | heap2 | tableam_tblselectinto_heapx - v | | tableam_view_heapx + relkind | amname | relname +---------+----------+----------------------------- + f | | tableam_fdw_heapx + r | heap2 | tableam_parted_1_heapx + r | heap | tableam_parted_2_heapx + p | | tableam_parted_heapx + S | seqlocal | tableam_seq_heapx + r | heap2 | tableam_tbl_heapx + r | heap2 | tableam_tblas_heapx + m | heap2 | tableam_tblmv_heapx + r | heap2 | tableam_tblselectinto_heapx + v | | tableam_view_heapx (10 rows) -- don't want to keep those tables, nor the default @@ -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_dump support +-- Checks for sequence access methods +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_local_sequenceam_handler; +-- Create and use sequence +CREATE SEQUENCE test_seqam USING local2; +SELECT nextval('test_seqam'::regclass); + nextval +--------- + 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/expected/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 = a1.amhandler AND a1.amtype = 't' AND -----+--------+-----+--------- (0 rows) +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + oid | amname | oid | proname +-----+--------+-----+--------- +(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..e64e50bbb9 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -4962,31 +4962,33 @@ Indexes: -- check printing info about access methods \dA List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA * List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA h* List of access methods @@ -5011,32 +5013,34 @@ List of access methods \dA: extra argument "bar" ignored \dA+ - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ * - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ h* List of access methods diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql index 825aed325e..472289994e 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 FROM tableam_tbl_heap2; -- CREATE VIEW doesn't support USING CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2; --- 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 tableam_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; --- 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 = 'seqlocal'; 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; -- we intentionally leave the objects created above alive, to verify pg_dump support + +-- Checks for sequence access methods + +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_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 = a1.amhandler AND a1.amtype = 't' AND OR p1.pronargs != 1 OR p1.proargtypes[0] != 'internal'::regtype); +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + -- **************** pg_amop **************** -- Look for illegal values in pg_amop fields diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 61ad417cde..84ef66a0ef 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2569,6 +2569,7 @@ SeqScan SeqScanState SeqTable SeqTableData +SequenceAmRoutine SerCommitSeqNo SerialControl SerialIOData @@ -3583,6 +3584,7 @@ lineno_t list_sort_comparator local_relopt local_relopts +local_sequence_magic local_source local_ts_iter local_ts_radix_tree @@ -3862,7 +3864,6 @@ scram_state scram_state_enum security_class_t sem_t -sepgsql_context_info_t sequence_magic set_join_pathlist_hook_type set_rel_pathlist_hook_type @@ -4083,6 +4084,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 @@ -4094,7 +4096,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 -- 2.45.1 [text/x-diff] v6-0006-Sequence-access-methods-dump-restore-support.patch (21.0K, ../../[email protected]/7-v6-0006-Sequence-access-methods-dump-restore-support.patch) download | inline diff: From 4452ea9622f0eecf439e5b1dc09ac46e3fb8202a Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Thu, 25 Apr 2024 17:28:27 +0900 Subject: [PATCH v6 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 | 49 +++++++++++++++++++++ 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, 205 insertions(+), 5 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_backup_archiver.c index 68e321212d..a2a8340473 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -174,6 +174,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) dopt->outputSuperuser = ropt->superuser; dopt->outputCreateDB = ropt->createDB; dopt->outputNoOwner = ropt->noOwner; + dopt->outputNoSequenceAm = ropt->noSequenceAm; dopt->outputNoTableAm = ropt->noTableAm; dopt->outputNoTablespaces = ropt->noTablespace; dopt->disable_triggers = ropt->disable_triggers; @@ -1224,6 +1225,7 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId, newToc->tag = pg_strdup(opts->tag); newToc->namespace = opts->namespace ? pg_strdup(opts->namespace) : NULL; newToc->tablespace = opts->tablespace ? pg_strdup(opts->tablespace) : NULL; + newToc->sequenceam = opts->sequenceam ? pg_strdup(opts->sequenceam) : NULL; newToc->tableam = opts->tableam ? pg_strdup(opts->tableam) : NULL; newToc->relkind = opts->relkind; newToc->owner = opts->owner ? pg_strdup(opts->owner) : NULL; @@ -2379,6 +2381,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, AH->currUser = NULL; /* unknown */ AH->currSchema = NULL; /* ditto */ + AH->currSequenceAm = NULL; /* ditto */ AH->currTablespace = NULL; /* ditto */ AH->currTableAm = NULL; /* ditto */ @@ -2608,6 +2611,7 @@ WriteToc(ArchiveHandle *AH) WriteStr(AH, te->copyStmt); WriteStr(AH, te->namespace); WriteStr(AH, te->tablespace); + WriteStr(AH, te->sequenceam); WriteStr(AH, te->tableam); WriteInt(AH, te->relkind); WriteStr(AH, te->owner); @@ -2712,6 +2716,9 @@ ReadToc(ArchiveHandle *AH) if (AH->version >= K_VERS_1_10) te->tablespace = ReadStr(AH); + if (AH->version >= K_VERS_1_17) + te->sequenceam = ReadStr(AH); + if (AH->version >= K_VERS_1_14) te->tableam = ReadStr(AH); @@ -3367,6 +3374,9 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname) free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; + free(AH->currTableAm); AH->currTableAm = NULL; @@ -3529,6 +3539,57 @@ _selectTablespace(ArchiveHandle *AH, const char *tablespace) destroyPQExpBuffer(qry); } +/* + * Set the proper default_sequence_access_method value for the sequence. + */ +static void +_selectSequenceAccessMethod(ArchiveHandle *AH, const char *sequenceam) +{ + RestoreOptions *ropt = AH->public.ropt; + PQExpBuffer cmd; + const char *want, + *have; + + /* do nothing in --no-sequence-access-method mode */ + if (ropt->noSequenceAm) + return; + + have = AH->currSequenceAm; + want = sequenceam; + + if (!want) + return; + + if (have && strcmp(want, have) == 0) + return; + + cmd = createPQExpBuffer(); + appendPQExpBuffer(cmd, + "SET default_sequence_access_method = %s;", + fmtId(want)); + + if (RestoringToDB(AH)) + { + PGresult *res; + + res = PQexec(AH->connection, cmd->data); + + if (!res || PQresultStatus(res) != 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 = pg_strdup(want); +} + /* * Set the proper default_table_access_method value for the table. */ @@ -3738,6 +3799,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData) _becomeOwner(AH, te); _selectOutputSchema(AH, te->namespace); _selectTablespace(AH, te->tablespace); + _selectSequenceAccessMethod(AH, te->sequenceam); if (te->relkind != RELKIND_PARTITIONED_TABLE) _selectTableAccessMethod(AH, te->tableam); @@ -4240,6 +4302,8 @@ restore_toc_entries_prefork(ArchiveHandle *AH, TocEntry *pending_list) AH->currUser = NULL; free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; free(AH->currTablespace); AH->currTablespace = NULL; free(AH->currTableAm); @@ -4979,6 +5043,7 @@ CloneArchive(ArchiveHandle *AH) clone->connCancel = NULL; clone->currUser = NULL; clone->currSchema = NULL; + clone->currSequenceAm = NULL; clone->currTableAm = NULL; clone->currTablespace = NULL; @@ -5038,6 +5103,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_backup_archiver.h index ce5ed1dd39..cdfebcd340 100644 --- a/src/bin/pg_dump/pg_backup_archiver.h +++ b/src/bin/pg_dump/pg_backup_archiver.h @@ -71,10 +71,11 @@ #define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0) /* BLOB METADATA entries * and multiple BLOBS, * relkind */ +#define K_VERS_1_17 MAKE_ARCHIVE_VERSION(1, 17, 0) /* add sequenceam */ /* 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_VERS_REV) @@ -322,6 +323,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 */ @@ -353,6 +355,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 relkind; /* relation kind, only for TABLE tags */ char *owner; @@ -394,6 +397,7 @@ typedef struct _archiveOpts const char *tag; const char *namespace; const char *tablespace; + const char *sequenceam; const char *tableam; char relkind; const char *owner; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 4b1674c5df..442bffd275 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 = dopt.outputSuperuser; ropt->createDB = dopt.outputCreateDB; ropt->noOwner = dopt.outputNoOwner; + ropt->noSequenceAm = dopt.outputNoSequenceAm; ropt->noTableAm = dopt.outputNoTableAm; ropt->noTablespace = dopt.outputNoTablespaces; ropt->disable_triggers = 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 assignments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); + printf(_(" --no-sequence-access-method do not sequence table access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); @@ -13110,6 +13113,9 @@ dumpAccessMethod(Archive *fout, const AccessMethodInfo *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; @@ -17297,7 +17303,8 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) *maxv, *minv, *cache, - *seqtype; + *seqtype, + *seqam; bool cycled; bool is_ascending; int64 default_minv, @@ -17311,13 +17318,35 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) qseqname = pg_strdup(fmtId(tbinfo->dobj.name)); - if (fout->remoteVersion >= 100000) + if (fout->remoteVersion >= 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 = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam) " + "WHERE s.seqrelid = '%u'::oid", + tbinfo->dobj.catId.oid); + } + else if (fout->remoteVersion >= 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, " + "'seqlocal' AS seqam " "FROM pg_catalog.pg_sequence " "WHERE seqrelid = '%u'::oid", tbinfo->dobj.catId.oid); @@ -17333,7 +17362,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, 'seqlocal' as seqam FROM %s", fmtQualifiedDumpable(tbinfo)); } @@ -17352,6 +17381,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) minv = PQgetvalue(res, 0, 4); cache = PQgetvalue(res, 0, 5); cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0); + seqam = PQgetvalue(res, 0, 7); /* Calculate default limits for a sequence of this type */ is_ascending = (incby[0] != '-'); @@ -17483,6 +17513,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) ARCHIVE_OPTS(.tag = tbinfo->dobj.name, .namespace = tbinfo->dobj.namespace->dobj.name, .owner = tbinfo->rolname, + .sequenceam = seqam, .description = "SEQUENCE", .section = SECTION_PRE_DATA, .createStmt = query->data, diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index 882dbf8e86..a35bd3a044 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 = 0; static int disable_triggers = 0; static int if_exists = 0; static int inserts = 0; +static int no_sequence_access_method = 0; static int no_table_access_method = 0; static int no_tablespaces = 0; static int use_setsessauth = 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 assignments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); printf(_(" --no-sync do not wait for changes to be written safely to disk\n")); + printf(_(" --no-sequence-access-method do not dump sequence access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c index df119591cc..081954c5ce 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 = 0; static int if_exists = 0; static int no_data_for_failed_tables = 0; + static int outputNoSequenceAm = 0; static int outputNoTableAm = 0; static int outputNoTablespaces = 0; static int use_setsessauth = 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 = disable_triggers; opts->enable_row_security = enable_row_security; opts->noDataForFailedTables = no_data_for_failed_tables; + opts->noSequenceAm = outputNoSequenceAm; opts->noTableAm = outputNoTableAm; opts->noTablespace = outputNoTablespaces; opts->use_setsessauth = 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 access methods\n")); printf(_(" --no-table-access-method do not restore table access methods\n")); printf(_(" --no-tablespaces do not restore tablespace assignments\n")); printf(_(" --section=SECTION restore named section (pre-data, 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_dump.pl index d3dd8784d6..1f437f0ee9 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 = ( 'postgres', ], }, + no_sequence_access_method => { + dump_cmd => [ + 'pg_dump', '--no-sync', + "--file=$tempdir/no_sequence_access_method.sql", + '--no-sequence-access-method', 'postgres', + ], + }, no_table_access_method => { dump_cmd => [ 'pg_dump', '--no-sync', @@ -711,6 +718,7 @@ my %full_runs = ( no_large_objects => 1, no_owner => 1, no_privs => 1, + no_sequence_access_method => 1, no_table_access_method => 1, pg_dumpall_dbprivs => 1, pg_dumpall_exclude => 1, @@ -4488,6 +4496,18 @@ my %tests = ( }, }, + 'CREATE ACCESS METHOD regress_test_sequence_am' => { + create_order => 11, + create_sql => + 'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;', + regexp => qr/^ + \QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;\E + \n/xm, + like => { + %full_runs, section_pre_data => 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 @@ -4516,6 +4536,35 @@ my %tests = ( }, }, + + # 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' => { + create_order => 12, + create_sql => ' + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_0 USING seqlocal; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_1 USING regress_sequence_am; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_2 USING seqlocal;', + regexp => qr/^ + \QSET default_sequence_access_method = 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 => { + %full_runs, %dump_test_schema_runs, section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + no_sequence_access_method => 1, + only_dump_measurement => 1, + }, + }, + 'CREATE MATERIALIZED VIEW regress_pg_dump_matview_am' => { create_order => 13, create_sql => ' diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index b95ed87517..abe78c0d36 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -1109,6 +1109,23 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + + <para> + This option is ignored when emitting an archive (non-text) output + file. For the archive formats, you can specify the option when you + call <command>pg_restore</command>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml 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 <replaceable class="parameter">PATTERN</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml 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 </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> -- 2.45.1 [text/x-diff] v6-0007-Sequence-access-methods-core-documentation.patch (9.6K, ../../[email protected]/8-v6-0007-Sequence-access-methods-core-documentation.patch) download | inline diff: From d1bb7871bc6aac53cc7e6fc38be443cc3ab65bf7 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:55:56 +0900 Subject: [PATCH v6 7/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 698169afdb..b4bce394e4 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8962,6 +8962,22 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-default-sequence-access-method" xreflabel="default_sequence_access_method"> + <term><varname>default_sequence_access_method</varname> (<type>string</type>) + <indexterm> + <primary><varname>default_sequence_access_method</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + This parameter specifies the default sequence access method to use when + creating sequences if the <command>CREATE SEQUENCE</command> + command does not explicitly specify an access method. The default is + <literal>local</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-default-tablespace" xreflabel="default_tablespace"> <term><varname>default_tablespace</varname> (<type>string</type>) <indexterm> 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 @@ <!ENTITY planstats SYSTEM "planstats.sgml"> <!ENTITY tableam SYSTEM "tableam.sgml"> <!ENTITY indexam SYSTEM "indexam.sgml"> +<!ENTITY sequenceam SYSTEM "sequenceam.sgml"> <!ENTITY nls SYSTEM "nls.sgml"> <!ENTITY plhandler SYSTEM "plhandler.sgml"> <!ENTITY fdwhandler SYSTEM "fdwhandler.sgml"> 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 <replaceable class="parameter">name</replaceable> <listitem> <para> This clause specifies the type of access method to define. - Only <literal>TABLE</literal> and <literal>INDEX</literal> - are supported at present. + Only <literal>TABLE</literal>, <literal>SEQUENCE</literal> and + <literal>INDEX</literal> are supported at present. </para> </listitem> </varlistentry> @@ -77,12 +77,15 @@ CREATE ACCESS METHOD <replaceable class="parameter">name</replaceable> declared to take a single argument of type <type>internal</type>, and its return type depends on the type of access method; for <literal>TABLE</literal> access methods, it must - be <type>table_am_handler</type> and for <literal>INDEX</literal> - access methods, it must be <type>index_am_handler</type>. + be <type>table_am_handler</type>; for <literal>INDEX</literal> + access methods, it must be <type>index_am_handler</type>; + for <literal>SEQUENCE</literal>, it must be + <type>sequence_am_handler</type>; 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 <xref linkend="tableam"/> and the index access method - API is described in <xref linkend="indexam"/>. + is described in <xref linkend="tableam"/>, the index access method + API is described in <xref linkend="indexam"/> and the sequence access + method is described in <xref linkend="sequenceam"/>. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/create_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 ] <replaceab [ MINVALUE <replaceable class="parameter">minvalue</replaceable> | NO MINVALUE ] [ MAXVALUE <replaceable class="parameter">maxvalue</replaceable> | NO MAXVALUE ] [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ [ NO ] CYCLE ] [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ] + [ USING <replaceable class="parameter">access_method</replaceable> ] </synopsis> </refsynopsisdiv> @@ -261,6 +262,17 @@ SELECT * FROM <replaceable>name</replaceable>; </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>USING</literal> <replaceable class="parameter">access_method</replaceable></term> + <listitem> + <para> + The <literal>USING</literal> option specifies which sequence access + method will be used when generating the sequence numbers. The default + is <literal>local</literal>. + </para> + </listitem> + </varlistentry> </variablelist> </refsect1> 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 @@ +<!-- doc/src/sgml/sequenceam.sgml --> + +<chapter id="sequenceam"> + <title>Sequence Access Method Interface Definition</title> + + <indexterm> + <primary>Sequence Access Method</primary> + </indexterm> + <indexterm> + <primary>sequenceam</primary> + <secondary>Sequence Access Method</secondary> + </indexterm> + + <para> + This chapter explains the interface between the core + <productname>PostgreSQL</productname> system and <firstterm>sequence access + methods</firstterm>, 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. + </para> + + <para> + Each sequence access method is described by a row in the + <link linkend="catalog-pg-am"><structname>pg_am</structname></link> system + catalog. The <structname>pg_am</structname> entry specifies a name and a + <firstterm>handler function</firstterm> for the sequence access method. These + entries can be created and deleted using the + <xref linkend="sql-create-access-method"/> and + <xref linkend="sql-drop-access-method"/> SQL commands. + </para> + + <para> + A sequence access method handler function must be declared to accept a single + argument of type <type>internal</type> and to return the pseudo-type + <type>sequence_am_handler</type>. The argument is a dummy value that simply + 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 + <structname>SequenceAmRoutine</structname>, which contains everything that the + core code needs to know to make use of the sequence access method. The return + value needs to be of server lifetime, which is typically achieved by + defining it as a <literal>static const</literal> variable in global + scope. The <structname>SequenceAmRoutine</structname> struct, also called the + access method's <firstterm>API struct</firstterm>, 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 + <structname>SequenceAmRoutine</structname> structure (with comments inside + 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 <ulink url="https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/include/access/sequenceam.h;hb=HEAD"> + <filename>src/include/access/sequenceam.h</filename></ulink> file. + </para> + + <para> + Currently, the way a sequence access method stores data is fairly + unconstrained, and it is possible to use a predefined + <link linkend="tableam">Table Access Method</link> to store sequence + data. + </para> + + <para> + For crash safety, a sequence access method can use + <link linkend="wal"><acronym>WAL</acronym></link>, or a custom + implementation. + If <acronym>WAL</acronym> is chosen, either + <link linkend="generic-wal">Generic WAL Records</link> can be used, or a + <link linkend="custom-rmgr">Custom WAL Resource Manager</link> can be + implemented. + </para> + + <para> + Any developer of a new <literal>sequence access method</literal> can refer to + the existing <literal>local</literal> implementation present in + <filename>src/backend/access/sequence/local.c</filename> for details of + its implementation. + </para> +</chapter> -- 2.45.1 [text/x-diff] v6-0008-snowflake-Add-sequence-AM-based-on-it.patch (28.0K, ../../[email protected]/9-v6-0008-snowflake-Add-sequence-AM-based-on-it.patch) download | inline diff: From b07b25138ea90ac447f6bd36d7c6d4d8dc405a69 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 26 Apr 2024 15:09:00 +0900 Subject: [PATCH v6 8/8] snowflake: Add sequence AM based on it This includes documentation and a basic implementation, though this is in a very early stage. --- doc/src/sgml/contrib.sgml | 1 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/snowflake.sgml | 100 ++++ contrib/Makefile | 1 + contrib/meson.build | 1 + contrib/snowflake/.gitignore | 3 + contrib/snowflake/Makefile | 19 + contrib/snowflake/expected/snowflake.out | 73 +++ contrib/snowflake/meson.build | 34 ++ contrib/snowflake/snowflake--1.0.sql | 21 + contrib/snowflake/snowflake.c | 570 +++++++++++++++++++++++ contrib/snowflake/snowflake.control | 5 + contrib/snowflake/sql/snowflake.sql | 29 ++ 13 files changed, 858 insertions(+) create mode 100644 doc/src/sgml/snowflake.sgml create mode 100644 contrib/snowflake/.gitignore create mode 100644 contrib/snowflake/Makefile create mode 100644 contrib/snowflake/expected/snowflake.out create mode 100644 contrib/snowflake/meson.build create mode 100644 contrib/snowflake/snowflake--1.0.sql create mode 100644 contrib/snowflake/snowflake.c create mode 100644 contrib/snowflake/snowflake.control create mode 100644 contrib/snowflake/sql/snowflake.sql diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml index 44639a8dca..e7b8871e8a 100644 --- a/doc/src/sgml/contrib.sgml +++ b/doc/src/sgml/contrib.sgml @@ -166,6 +166,7 @@ CREATE EXTENSION <replaceable>extension_name</replaceable>; &seg; &sepgsql; &contrib-spi; + &snowflake; &sslinfo; &tablefunc; &tcn; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 4e1218aa89..3b9e38e320 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -156,6 +156,7 @@ <!ENTITY seg SYSTEM "seg.sgml"> <!ENTITY contrib-spi SYSTEM "contrib-spi.sgml"> <!ENTITY sepgsql SYSTEM "sepgsql.sgml"> +<!ENTITY snowflake SYSTEM "snowflake.sgml"> <!ENTITY sslinfo SYSTEM "sslinfo.sgml"> <!ENTITY tablefunc SYSTEM "tablefunc.sgml"> <!ENTITY tcn SYSTEM "tcn.sgml"> diff --git a/doc/src/sgml/snowflake.sgml b/doc/src/sgml/snowflake.sgml new file mode 100644 index 0000000000..060699e7ec --- /dev/null +++ b/doc/src/sgml/snowflake.sgml @@ -0,0 +1,100 @@ +<!-- doc/src/sgml/snowflake.sgml --> + +<sect1 id="snowflake" xreflabel="snowflake"> + <title>snowflake — sequence access method</title> + + <indexterm zone="snowflake"> + <primary>snowflake</primary> + </indexterm> + + <para> + <literal>snowflake</literal> provides a sequence access method based on + <ulink url="https://en.wikipedia.org/wiki/Snowflake_ID">Snowflake IDs</ulink>. + </para> + + <para> + A Snowflake ID (or snowflake) is a unique 64-bit identifier made of three + components: + <itemizedlist spacing="compact"> + <listitem><para>41 bits for a timestamp, epoch-adjusted in milli-seconds</para></listitem> + <listitem><para>10 bits for machine ID</para></listitem> + <listitem><para>12 bits for a sequence number</para></listitem> + </itemizedlist> + </para> + + <sect2 id="snowflake-functions"> + <title>Functions</title> + + <variablelist> + <varlistentry> + <term> + <function>snowflake_get(raw int8) returns record</function> + <indexterm> + <primary>snowflake_get</primary> + <secondary>function</secondary> + </indexterm> + </term> + + <listitem> + <para> + Returns a record made of the timestamp in milli-seconds, the machine ID + and the sequence number for a single snowflake ID. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-parameters"> + <title>Configuration Parameters</title> + + <variablelist> + <varlistentry> + <term> + <varname>snowflake.machine_id</varname> + <indexterm> + <primary><varname>snowflake.machine_id</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Machine ID assigned to the snowflake IDs used in the sequence. The + default value is <literal>1</literal>. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-examples"> + <title>Examples</title> + + <para> + This is an example of creating a snowflake sequence: + </para> + +<programlisting> +CREATE SEQUENCE snowflake_seq USING snowflake; +</programlisting> + + <para> + Similarly to the default sequence access method, snowflake sequences + can be queried as a table: + </para> + +<programlisting> + =# SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) +=# SELECT to_timestamp(time_ms / 1000), machine, counter + FROM snowflake_get(nextval('snowflake_seq')); + to_timestamp | machine | counter +------------------------+---------+--------- + 2024-04-26 14:28:26+09 | 1 | 3 +(1 row) +</programlisting> + </sect2> + +</sect1> diff --git a/contrib/Makefile b/contrib/Makefile index abd780f277..f5d1b568b2 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -42,6 +42,7 @@ SUBDIRS = \ pg_walinspect \ postgres_fdw \ seg \ + snowflake \ spi \ tablefunc \ tcn \ diff --git a/contrib/meson.build b/contrib/meson.build index 14a8906865..4f2603e7e9 100644 --- a/contrib/meson.build +++ b/contrib/meson.build @@ -57,6 +57,7 @@ subdir('pg_walinspect') subdir('postgres_fdw') subdir('seg') subdir('sepgsql') +subdir('snowflake') subdir('spi') subdir('sslinfo') # start-scripts doesn't contain build products diff --git a/contrib/snowflake/.gitignore b/contrib/snowflake/.gitignore new file mode 100644 index 0000000000..44d119cfcc --- /dev/null +++ b/contrib/snowflake/.gitignore @@ -0,0 +1,3 @@ +# Generated subdirectories +/log/ +/results/ diff --git a/contrib/snowflake/Makefile b/contrib/snowflake/Makefile new file mode 100644 index 0000000000..fa5b48d565 --- /dev/null +++ b/contrib/snowflake/Makefile @@ -0,0 +1,19 @@ +# contrib/snowflake/Makefile + +MODULES = snowflake + +EXTENSION = snowflake +DATA = snowflake--1.0.sql + +REGRESS = snowflake + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/snowflake +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/snowflake/expected/snowflake.out b/contrib/snowflake/expected/snowflake.out new file mode 100644 index 0000000000..b7e469bf73 --- /dev/null +++ b/contrib/snowflake/expected/snowflake.out @@ -0,0 +1,73 @@ +CREATE EXTENSION snowflake; +CREATE SEQUENCE snowflake_seq USING snowflake; +SET snowflake.machine_id = 2000; -- error +ERROR: 2000 is outside the valid range for parameter "snowflake.machine_id" (0 .. 1023) +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(lastval()); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 3 | t +(1 row) + +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + relpersistence +---------------- + u +(1 row) + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; + machine | counter +---------+--------- + 4 | 2 + 4 | 3 + 4 | 4 + 4 | 5 + 4 | 6 + 4 | 7 + 4 | 8 + 4 | 9 + 4 | 10 + 4 | 11 +(10 rows) + +DROP TABLE snowflake_tab; +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; diff --git a/contrib/snowflake/meson.build b/contrib/snowflake/meson.build new file mode 100644 index 0000000000..567669eea7 --- /dev/null +++ b/contrib/snowflake/meson.build @@ -0,0 +1,34 @@ +# Copyright (c) 2024, PostgreSQL Global Development Group + +snowflake_sources = files( + 'snowflake.c', +) + +if host_system == 'windows' + snowflake_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'snowflake', + '--FILEDESC', 'snowflake - sequence access method',]) +endif + +snowflake = shared_module('snowflake', + snowflake_sources, + kwargs: contrib_mod_args, +) +contrib_targets += bloom + +install_data( + 'snowflake.control', + 'snowflake--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'snowflake', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'snowflake', + ], + }, +} diff --git a/contrib/snowflake/snowflake--1.0.sql b/contrib/snowflake/snowflake--1.0.sql new file mode 100644 index 0000000000..bcb9d754f1 --- /dev/null +++ b/contrib/snowflake/snowflake--1.0.sql @@ -0,0 +1,21 @@ +/* contrib/snowflake/snowflake--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION snowflake" to load this file. \quit + +CREATE FUNCTION snowflake_sequenceam_handler(internal) + RETURNS sequence_am_handler + AS 'MODULE_PATHNAME' + LANGUAGE C; + +CREATE ACCESS METHOD snowflake + TYPE SEQUENCE HANDLER snowflake_sequenceam_handler; +COMMENT ON ACCESS METHOD snowflake IS 'snowflake sequence access method'; + +CREATE FUNCTION snowflake_get(IN raw int8, + OUT time_ms int8, + OUT machine int4, + OUT counter int4) + RETURNS record + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT diff --git a/contrib/snowflake/snowflake.c b/contrib/snowflake/snowflake.c new file mode 100644 index 0000000000..be4c4039ec --- /dev/null +++ b/contrib/snowflake/snowflake.c @@ -0,0 +1,570 @@ +/*------------------------------------------------------------------------- + * + * snowflake.c + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * contrib/snowflake/snowflake.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <sys/time.h> + +#include "access/generic_xlog.h" +#include "access/sequenceam.h" +#include "access/xact.h" +#include "catalog/storage_xlog.h" +#include "commands/tablecmds.h" +#include "fmgr.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "storage/bufmgr.h" +#include "utils/guc.h" +#include "utils/numeric.h" + +PG_MODULE_MAGIC; + +/* "special area" of a snowflake's buffer page. */ +#define SNOWFLAKE_MAGIC 0x01 + +typedef struct snowflake_magic +{ + uint32 magic; +} snowflake_magic; + +/* ----------------------------------------------------------------------- + * Snowflake ID are 64-bit based, with the following structure: + * - 41 bits for an epoch-based timestamp, in milli-seconds. + * - 10 bits for a machine ID. + * - 12 bits for a sequence counter. + * + * The timestamp can be cut to an offset. The machine ID is controlled + * by a superuser GUC. Sequence properties apply to the sequence counter, + * as the other two are environment-dependent. + * ----------------------------------------------------------------------- + */ + +/* + * Helper routines to convert a snowflake ID from/to an int64. + */ +#define SNOWFLAKE_COUNTER_MASK 0x0000000000000FFF /* 12 bits */ +#define SNOWFLAKE_COUNTER_SHIFT 0 +#define SNOWFLAKE_MACHINE_ID_MASK 0x00000000000003FF /* 10 bits */ +#define SNOWFLAKE_MACHINE_ID_SHIFT 12 /* counter */ +#define SNOWFLAKE_TIMESTAMP_MASK 0x000001FFFFFFFFFF /* 41 bits */ +#define SNOWFLAKE_TIMESTAMP_SHIFT 22 /* machine ID + counter sizes */ + +typedef struct snowflake_id +{ + uint32 count; + uint32 machine; + uint64 time_ms; +} snowflake_id; + +#define snowflake_id_to_int64(id, raw) { \ + raw = (((id).count) & SNOWFLAKE_COUNTER_MASK) << SNOWFLAKE_COUNTER_SHIFT | \ + (((id).machine) & SNOWFLAKE_MACHINE_ID_MASK) << SNOWFLAKE_MACHINE_ID_SHIFT | \ + (((id).time_ms) & SNOWFLAKE_TIMESTAMP_MASK) << SNOWFLAKE_TIMESTAMP_SHIFT; \ +} + +#define int64_to_snowflake_id(raw, id) { \ + (id).count = ((raw) >> SNOWFLAKE_COUNTER_SHIFT) & SNOWFLAKE_COUNTER_MASK; \ + (id).machine = ((raw) >> SNOWFLAKE_MACHINE_ID_SHIFT) & SNOWFLAKE_MACHINE_ID_MASK; \ + (id).time_ms = ((raw) >> SNOWFLAKE_TIMESTAMP_SHIFT) & SNOWFLAKE_TIMESTAMP_MASK; \ +} + +/* + * Format of tuples stored in heap table associated to snowflake sequence. + */ +typedef struct FormData_snowflake_data +{ + /* enough to cover 12 bits of the internal counter */ + int16 count; + bool is_called; +} FormData_snowflake_data; + +typedef FormData_snowflake_data *Form_snowflake_data; + +/* + * Columns of a snowflake sequence relation. + */ +#define SNOWFLAKE_COL_COUNT 1 +#define SNOWFLAKE_COL_CALLED 2 + +#define SNOWFLAKE_COLS 2 + +/* GUC parameter */ +static int snowflake_machine_id = 1; + +PG_FUNCTION_INFO_V1(snowflake_sequenceam_handler); + +/* ----------------------------------------------------------------------- + * Interfaces for relation manipulation. + * ----------------------------------------------------------------------- + */ + +/* + * Initialize snowflake relation's fork with some data. + */ +static void +fill_snowflake_fork(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + snowflake_magic *sm; + OffsetNumber offnum; + GenericXLogState *state = NULL; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(snowflake_magic)); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + sm->magic = SNOWFLAKE_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= HEAP_XMAX_INVALID; + ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber); + + /* + * Initialize before entering in the critical section, as this does + * allocations. + */ + if (forkNum == INIT_FORKNUM) + state = GenericXLogStart(rel); + + START_CRIT_SECTION(); + + MarkBufferDirty(buf); + + offnum = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* + * Init forks have to be logged. These go through generic WAL records + * for simplicity's sake to save from the need of a custom RMGR. + */ + if (forkNum == INIT_FORKNUM) + { + (void) GenericXLogRegisterBuffer(state, buf, GENERIC_XLOG_FULL_IMAGE); + GenericXLogFinish(state); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * Initialize snowflake relation. + * + * This needs to handle both the initial and main forks. + */ +static void +fill_snowflake(Relation rel, HeapTuple tuple) +{ + SMgrRelation srel; + + Assert(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED); + + fill_snowflake_fork(rel, tuple, MAIN_FORKNUM); + + /* init fork */ + srel = smgropen(rel->rd_locator, INVALID_PROC_NUMBER); + smgrcreate(srel, INIT_FORKNUM, false); + log_smgrcreate(&rel->rd_locator, INIT_FORKNUM); + fill_snowflake_fork(rel, tuple, INIT_FORKNUM); + FlushRelationBuffers(rel); + smgrclose(srel); +} + +/* + * Read the current state of a snowflake sequence + * + * Given an opened sequence relation, lock the page buffer and find the tuple. + * + * *buf receives the reference to the pinned-and-ex-locked buffer. + * *seqdatatuple receives the reference to the sequence tuple proper. + * + * Returns value points to the data payload of the tuple. + */ +static Form_snowflake_data +read_snowflake(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + snowflake_magic *sm; + Form_snowflake_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SNOWFLAKE_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_snowflake_data) GETSTRUCT(seqdatatuple); + + return seq; +} + + +/* ------------------------------------------------------------------------ + * Callbacks for the snowflake 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 * +snowflake_sequenceam_get_table_am(void) +{ + return "heap"; +} + +/* + * snowflake_sequenceam_init + * + * Initialize relation of a snowflake sequence. This stores the sequence + * counter in an unlogged relation as timestamps ensure value unicity. + */ +static void +snowflake_sequenceam_init(Relation rel, int64 last_value, bool is_called) +{ + Datum values[SNOWFLAKE_COLS]; + bool nulls[SNOWFLAKE_COLS]; + int16 counter; + TupleDesc tupdesc; + HeapTuple tuple; + List *elts = NIL; + ListCell *lc; + ColumnDef *coldef = NULL; + AlterTableCmd *atcmd; + List *atcmds = NIL; + + /* Adjust last_value, depending on the defaults given */ + counter = ((int16) last_value) & SNOWFLAKE_COUNTER_MASK; + + /* + * Create unlogged relation with its attributes. + */ + coldef = makeColumnDef("count", INT2OID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + + foreach(lc, elts) + { + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + /* + * Switch the relation to be unlogged. This forces a rewrite, but + * the relation is empty so that's OK. + */ + RelationSetNewRelfilenumber(rel, RELPERSISTENCE_UNLOGGED); + + /* And insert its first tuple */ + values[0] = Int16GetDatum(counter); + nulls[0] = false; + values[1] = BoolGetDatum(is_called); + nulls[1] = false; + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, values, nulls); + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_nextval + * + * Return the next value for a snowflake sequence. + */ +static int64 +snowflake_sequenceam_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + Buffer buf; + Form_snowflake_data seq; + HeapTupleData seqdatatuple; + int64 result = 0; + snowflake_id id = {0}; + struct timeval tp; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* + * The logic here is quite simple, increment the counter until its + * threshold is reached and get back to the start. If the threshold + * is reached, wait 1ms to ensure a unique timestamp. There is no + * need to do a retry as the buffer is already locked. + */ + id.count = seq->count; + id.count++; + + if (id.count > (PG_INT16_MAX & SNOWFLAKE_COUNTER_MASK)) + { + /* + * Threshold reached, so wait a bit for force clock to a new + * timestamp. + */ + id.count = 1; + pg_usleep(1000L); /* 1ms */ + } + + /* Compute timestamp, with buffer locked */ + gettimeofday(&tp, NULL); + id.time_ms = (uint64) tp.tv_sec * 1000 + + tp.tv_usec / 1000; + + /* Machine ID */ + id.machine = snowflake_machine_id; + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->count = id.count; + seq->is_called = true; + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + /* Store the last value computed for lastval() */ + snowflake_id_to_int64(id, result); + *last = result; + return result; +} + +/* + * snowflake_sequenceam_setval + * + * Set the sequence value, manipulating only the sequence counter. + */ +static void +snowflake_sequenceam_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* Change the in-buffer tuple */ + START_CRIT_SECTION(); + seq->count = (next & SNOWFLAKE_COUNTER_MASK); + seq->is_called = iscalled; + MarkBufferDirty(buf); + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_get_state + * + * Return the last sequence counter value. + */ +static void +snowflake_sequenceam_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + seq = read_snowflake(rel, &buf, &seqdatatuple); + *last_value = seq->count; + *is_called = seq->is_called; + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_reset + * + * Reset the sequence, coming down to resetting its counter. + */ +static void +snowflake_sequenceam_reset(Relation rel, int64 startv, bool is_called, + bool reset_state) +{ + HeapTupleData seqdatatuple; + HeapTuple tuple; + Form_snowflake_data seq; + Buffer buf; + + /* lock buffer page and read tuple */ + (void) read_snowflake(rel, &buf, &seqdatatuple); + + /* copy the existing tuple */ + tuple = 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 = (Form_snowflake_data) GETSTRUCT(tuple); + seq->count = (startv & SNOWFLAKE_COUNTER_MASK); + seq->is_called = is_called; + + /* create new storage */ + RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence); + + /* insert the modified tuple into the page */ + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_change_persistence + * + * There is nothing to do here, the underneath relation has to remain + * unlogged and is set as such when creating the sequence. + */ +static void +snowflake_sequenceam_change_persistence(Relation rel, char newrelpersistence) +{ + /* Nothing to do here */ +} + +/* ------------------------------------------------------------------------ + * Definition of the snowflake sequence access method. + * ------------------------------------------------------------------------ + */ + +static const SequenceAmRoutine snowflake_sequenceam_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = snowflake_sequenceam_get_table_am, + .init = snowflake_sequenceam_init, + .nextval = snowflake_sequenceam_nextval, + .setval = snowflake_sequenceam_setval, + .get_state = snowflake_sequenceam_get_state, + .reset = snowflake_sequenceam_reset, + .change_persistence = snowflake_sequenceam_change_persistence +}; + +Datum +snowflake_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&snowflake_sequenceam_methods); +} + +/* Utility functions */ + +/* + * snowflake_get + * + * Return a tuple worth of snowflake ID data, in a readable shape. + */ +PG_FUNCTION_INFO_V1(snowflake_get); +Datum +snowflake_get(PG_FUNCTION_ARGS) +{ +#define SNOWFLAKE_GET_COLS 3 + int64 raw = PG_GETARG_INT64(0); + Datum *values; + bool *nulls; + TupleDesc tupdesc; + snowflake_id id; + + /* determine result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + int64_to_snowflake_id(raw, id); + + nulls = palloc0(sizeof(bool) * tupdesc->natts); + values = palloc0(sizeof(Datum) * tupdesc->natts); + + values[0] = Int64GetDatum(id.time_ms); + values[1] = Int32GetDatum(id.machine); + values[2] = Int32GetDatum(id.count); + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Entry point when loading extension. + */ +void +_PG_init(void) +{ + DefineCustomIntVariable("snowflake.machine_id", + "Machine ID to use with snowflake sequence.", + "Default value is 1.", + &snowflake_machine_id, + 1, 0, 1023, /* 10 bits as max */ + PGC_SUSET, + 0, NULL, NULL, NULL); +} diff --git a/contrib/snowflake/snowflake.control b/contrib/snowflake/snowflake.control new file mode 100644 index 0000000000..7b8c6089c2 --- /dev/null +++ b/contrib/snowflake/snowflake.control @@ -0,0 +1,5 @@ +# snowflake extension +comment = 'snowflake - sequence access method' +default_version = '1.0' +module_pathname = '$libdir/snowflake' +relocatable = true diff --git a/contrib/snowflake/sql/snowflake.sql b/contrib/snowflake/sql/snowflake.sql new file mode 100644 index 0000000000..395d166ba4 --- /dev/null +++ b/contrib/snowflake/sql/snowflake.sql @@ -0,0 +1,29 @@ +CREATE EXTENSION snowflake; + +CREATE SEQUENCE snowflake_seq USING snowflake; + +SET snowflake.machine_id = 2000; -- error +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(lastval()); +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; +DROP TABLE snowflake_tab; + +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; -- 2.45.1 [application/pgp-signature] signature.asc (833B, ../../[email protected]/10-signature.asc) download ^ permalink raw reply [nested|flat] 14+ messages in thread
* Re: Sequence Access Methods, round two @ 2024-08-26 04:45 Michael Paquier <[email protected]> parent: Michael Paquier <[email protected]> 0 siblings, 1 reply; 14+ messages in thread From: Michael Paquier @ 2024-08-26 04:45 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; +Cc: Tomas Vondra <[email protected]>; Peter Smith <[email protected]>; Postgres hackers <[email protected]> On Thu, Jun 20, 2024 at 03:12:32PM +0900, Michael Paquier wrote: > While on it, I have noticed a couple of conflicts while rebasing, so > attached is a refreshed patch set. Please find attached a new patch set for the next commit fest. The patch has required a bit of work to be able to work on HEAD, particularly around the fact that pg_sequence_read_tuple() is able to do the same work as the modifications done for pg_sequence_last_value() in the previous patch sets. I have modified the patch set to depend on that, and adapted pg_dump/restore to it. The dump/restore part has also required some tweaks to make sure that the AM is dumped depending on if --schema-only and if we care about the values. Finally, I have been rather annoyed by the addition of log_cnt in the new function pg_sequence_read_tuple(). This patch set could also implement a new system function, but it looks like a waste as we don't care about log_cnt in pg_dump and pg_upgrade on HEAD, so I'm proposing to remove it on a different thread: https://www.postgresql.org/message-id/Zsvka3r-y2ZoXAdH%40paquier.xyz -- Michael Attachments: [text/x-diff] v7-0001-Remove-log_cnt-from-pg_sequence_read_tuple.patch (3.6K, ../../[email protected]/2-v7-0001-Remove-log_cnt-from-pg_sequence_read_tuple.patch) download | inline diff: From b7b2f21e23157f1c744ef87651bdc8e0da68f713 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Mon, 26 Aug 2024 11:15:21 +0900 Subject: [PATCH v7 1/8] Remove log_cnt from pg_sequence_read_tuple() This will be used by the sequence AM patch, and log_cnt is something that sequence AMs can optionally use, at least in this design. --- src/include/catalog/pg_proc.dat | 4 ++-- src/backend/commands/sequence.c | 15 +++++++-------- src/test/regress/expected/sequence.out | 6 +++--- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4abc6d9526..986df08c6e 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -3332,8 +3332,8 @@ { 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}', + proallargtypes => '{regclass,int8,bool}', proargmodes => '{i,o,o}', + proargnames => '{sequence_oid,last_value,is_called}', prosrc => 'pg_sequence_read_tuple' }, { oid => '275', descr => 'return the next oid for a system table', diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 8c1131f020..11000b97bd 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -1783,21 +1783,20 @@ pg_sequence_parameters(PG_FUNCTION_ARGS) Datum pg_sequence_read_tuple(PG_FUNCTION_ARGS) { +#define SEQUENCE_READ_TUPLE_COLS 2 Oid relid = PG_GETARG_OID(0); SeqTable elm; Relation seqrel; - Datum values[SEQ_COL_LASTCOL] = {0}; - bool isnull[SEQ_COL_LASTCOL] = {0}; + Datum values[SEQUENCE_READ_TUPLE_COLS] = {0}; + bool isnull[SEQUENCE_READ_TUPLE_COLS] = {0}; TupleDesc resultTupleDesc; HeapTuple resultHeapTuple; Datum result; - resultTupleDesc = CreateTemplateTupleDesc(SEQ_COL_LASTCOL); + resultTupleDesc = CreateTemplateTupleDesc(SEQUENCE_READ_TUPLE_COLS); TupleDescInitEntry(resultTupleDesc, (AttrNumber) 1, "last_value", INT8OID, -1, 0); - TupleDescInitEntry(resultTupleDesc, (AttrNumber) 2, "log_cnt", - INT8OID, -1, 0); - TupleDescInitEntry(resultTupleDesc, (AttrNumber) 3, "is_called", + TupleDescInitEntry(resultTupleDesc, (AttrNumber) 2, "is_called", BOOLOID, -1, 0); resultTupleDesc = BlessTupleDesc(resultTupleDesc); @@ -1818,8 +1817,7 @@ pg_sequence_read_tuple(PG_FUNCTION_ARGS) seq = read_seq_tuple(seqrel, &buf, &seqtuple); values[0] = Int64GetDatum(seq->last_value); - values[1] = Int64GetDatum(seq->log_cnt); - values[2] = BoolGetDatum(seq->is_called); + values[1] = BoolGetDatum(seq->is_called); UnlockReleaseBuffer(buf); } @@ -1831,6 +1829,7 @@ pg_sequence_read_tuple(PG_FUNCTION_ARGS) resultHeapTuple = heap_form_tuple(resultTupleDesc, values, isnull); result = HeapTupleGetDatum(resultHeapTuple); PG_RETURN_DATUM(result); +#undef SEQUENCE_READ_TUPLE_COLS } diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expected/sequence.out index e749c4574e..6be5406ae9 100644 --- a/src/test/regress/expected/sequence.out +++ b/src/test/regress/expected/sequence.out @@ -841,9 +841,9 @@ SELECT nextval('test_seq1'); -- pg_sequence_read_tuple SELECT * FROM pg_sequence_read_tuple('test_seq1'); - last_value | log_cnt | is_called -------------+---------+----------- - 10 | 32 | t + last_value | is_called +------------+----------- + 10 | t (1 row) DROP SEQUENCE test_seq1; -- 2.45.2 [text/x-diff] v7-0002-Remove-FormData_pg_sequence_data-from-init_params.patch (9.2K, ../../[email protected]/3-v7-0002-Remove-FormData_pg_sequence_data-from-init_params.patch) download | inline diff: From fdc6a51e6a81f29b53e949285eb7d1d4024fb0aa Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:00:45 +0900 Subject: [PATCH v7 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/sequence.c index 11000b97bd..e4c0937435 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 rel, 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); 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 = makeNode(CreateStmt); @@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) /* 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); /* @@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) { case SEQ_COL_LASTVAL: coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(seqdataform.last_value); + value[i - 1] = Int64GetDatumFast(last_value); break; case SEQ_COL_LOG: coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); @@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) ObjectAddress address; Relation rel; HeapTuple seqtuple; + bool reset_state = false; + bool is_called; + int64 last_value; HeapTuple newdatatuple; /* 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 = heap_copytuple(&datatuple); newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); + last_value = newdataform->last_value; + is_called = newdataform->is_called; UnlockReleaseBuffer(buf); /* 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); /* 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 = last_value; + newdataform->is_called = is_called; + if (reset_state) + newdataform->log_cnt = 0; fill_seq_with_data(seqrel, newdatatuple); } @@ -1236,17 +1249,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). Also + * 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 overridden. * * Note: we force a sequence rewrite whenever we change parameters that affect - * 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. Currently, * 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 @@ -1257,7 +1272,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) { @@ -1360,11 +1377,11 @@ init_params(ParseState *pstate, List *options, bool for_identity, } /* - * 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 = 0; + *reset_state = true; /* AS type */ if (as_type != NULL) @@ -1413,7 +1430,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("INCREMENT must not be zero"))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1425,7 +1442,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, { seqform->seqcycle = boolVal(is_cycled->arg); Assert(BoolIsValid(seqform->seqcycle)); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1436,7 +1453,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (max_value != NULL && max_value->arg) { seqform->seqmax = defGetInt64(max_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || max_value != NULL || reset_max_value) { @@ -1452,7 +1469,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmax = -1; /* descending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate maximum value. No need to check INT8 as seqmax is an int64 */ @@ -1468,7 +1485,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (min_value != NULL && min_value->arg) { seqform->seqmin = defGetInt64(min_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || min_value != NULL || reset_min_value) { @@ -1484,7 +1501,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmin = 1; /* ascending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate minimum value. No need to check INT8 as seqmin is an int64 */ @@ -1535,30 +1552,30 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (restart_value != NULL) { if (restart_value->arg != NULL) - seqdataform->last_value = defGetInt64(restart_value); + *last_value = defGetInt64(restart_value); else - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; - seqdataform->log_cnt = 0; + *last_value = seqform->seqstart; + *is_called = false; + *reset_state = true; } else if (isInit) { - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; + *last_value = seqform->seqstart; + *is_called = false; } /* 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))); /* CACHE */ @@ -1570,7 +1587,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("CACHE (%lld) must be greater than zero", (long long) seqform->seqcache))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { -- 2.45.2 [text/x-diff] v7-0003-Integrate-addition-of-attributes-for-sequences-wi.patch (11.1K, ../../[email protected]/4-v7-0003-Integrate-addition-of-attributes-for-sequences-wi.patch) download | inline diff: From 9505540258c7d7d4bbafc4948af5d9945e4a9899 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 19 Jan 2024 15:00:14 +0900 Subject: [PATCH v7 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 124d853e49..f5fe0d2257 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2338,6 +2338,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/sequence.c index e4c0937435..1a4fe65dbf 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 = NIL; + List *atcmds = 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 = NIL; for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) { ColumnDef *coldef = NULL; @@ -198,7 +200,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) coldef->is_not_null = true; null[i - 1] = false; - stmt->tableElts = lappend(stmt->tableElts, coldef); + elts = lappend(elts, coldef); } stmt->relation = seq->sequence; @@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; + /* + * Initial relation has no attributes, these are added later. + */ + stmt->tableElts = NIL; address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + tupDesc = RelationGetDescr(rel); /* now initialize the sequence's data */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index dac39df83a..f6e3873b29 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4479,6 +4479,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 */ @@ -4782,6 +4783,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* Recursion occurs during execution phase */ pass = 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 = 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, @@ -5197,6 +5205,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 = ATExecAddColumn(wqueue, tab, rel, &cmd, cmd->recurse, false, @@ -6358,6 +6367,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 b2ea8125c9..6a65db0910 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1664,7 +1664,11 @@ ProcessUtilitySlow(ParseState *pstate, break; case T_CreateSeqStmt: + EventTriggerAlterTableStart(parsetree); address = DefineSequence(pstate, (CreateSeqStmt *) parsetree); + /* stashed internally */ + commandCollected = true; + EventTriggerAlterTableEnd(); break; case T_AlterSeqStmt: diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/test/modules/test_ddl_deparse/expected/alter_table.out index 6daa186a84..24b2179455 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 of sequence parent_b_seq NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type ADD COLUMN (and recurse) desc column b of table parent NOTICE: DDL test: type simple, tag ALTER SEQUENCE @@ -76,7 +79,10 @@ NOTICE: subcommand: type SET NOT NULL desc column a of table parent NOTICE: subcommand: type SET NOT NULL desc column a of table child NOTICE: subcommand: type SET NOT NULL desc column a of table grandchild 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 of 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 table parent diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out 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 of 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 2178ce83e9..2be95f99f8 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 of 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 of 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 of sequence datatype_table_is_small_seq NOTICE: DDL test: type simple, tag CREATE TABLE NOTICE: DDL test: type simple, tag CREATE INDEX NOTICE: DDL test: type simple, tag CREATE INDEX diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c index 2758ae82d7..280642e81c 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 = "ADD COLUMN"; break; + case AT_AddColumnToSequence: + strtype = "ADD COLUMN TO SEQUENCE"; + break; case AT_AddColumnToView: strtype = "ADD COLUMN TO VIEW"; break; -- 2.45.2 [text/x-diff] v7-0004-Refactor-code-for-in-core-local-sequences.patch (54.5K, ../../[email protected]/5-v7-0004-Refactor-code-for-in-core-local-sequences.patch) download | inline diff: From afb93fe43a9331a6c77a8441e961cadc08499a63 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Thu, 25 Apr 2024 16:49:47 +0900 Subject: [PATCH v7 4/8] Refactor code for in-core "local" sequences This commit restructures the code of in-core sequences into a new set of files: - seqdesc.c is renamed to seqlocaldesc.c. - seqlocalxlog.c to the code in the WAL replay logic. - seqlocalam.c to store a set of routines called from sequence.c, finishing the separation between the main sequence logic and the in-core sequences. - seqlocalam.h to store the AM-specific structures and routine related to the in-core sequences. WAL records are renamed to "SequenceLocal" with structures, variables and file structures mapping to that. --- src/include/access/rmgrlist.h | 2 +- src/include/access/seqlocalam.h | 58 ++ src/backend/access/rmgrdesc/Makefile | 2 +- src/backend/access/rmgrdesc/meson.build | 2 +- .../rmgrdesc/{seqdesc.c => seqlocaldesc.c} | 20 +- src/backend/access/sequence/Makefile | 2 +- src/backend/access/sequence/meson.build | 2 + src/backend/access/sequence/seqlocalam.c | 634 ++++++++++++++++++ src/backend/access/sequence/seqlocalxlog.c | 82 +++ src/backend/access/transam/rmgr.c | 1 + src/backend/commands/sequence.c | 634 +----------------- src/bin/pg_waldump/.gitignore | 2 +- src/bin/pg_waldump/rmgrdesc.c | 1 + src/bin/pg_waldump/t/001_basic.pl | 2 +- 14 files changed, 822 insertions(+), 622 deletions(-) create mode 100644 src/include/access/seqlocalam.h rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%) create mode 100644 src/backend/access/sequence/seqlocalam.c create mode 100644 src/backend/access/sequence/seqlocalxlog.c diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h index 78e6b908c6..fa94beb6ed 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, btree_identify, btree_xlog PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL, hash_mask, NULL) PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, 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, NULL, seq_mask, NULL) +PG_RMGR(RM_SEQ_LOCAL_ID, "SequenceLocal", seq_local_redo, seq_local_desc, seq_local_identify, NULL, NULL, seq_local_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, NULL, brin_mask, NULL) PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL, NULL, NULL) diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h new file mode 100644 index 0000000000..225fb9a2cb --- /dev/null +++ b/src/include/access/seqlocalam.h @@ -0,0 +1,58 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.h + * Local sequence access method. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/access/seqlocalam.h + * + *------------------------------------------------------------------------- + */ +#ifndef SEQLOCALAM_H +#define SEQLOCALAM_H + +#include "access/xlogreader.h" +#include "storage/relfilelocator.h" +#include "utils/rel.h" + +/* XLOG stuff */ +#define XLOG_SEQ_LOCAL_LOG 0x00 + +typedef struct xl_seq_local_rec +{ + RelFileLocator locator; + /* SEQUENCE TUPLE DATA FOLLOWS AT THE END */ +} xl_seq_local_rec; + +/* + * The "special area" of a local sequence's buffer page looks like this. + */ +#define SEQ_LOCAL_MAGIC 0x1717 + +typedef struct seq_local_magic +{ + uint32 magic; +} seq_local_magic; + +extern void seq_local_redo(XLogReaderState *record); +extern void seq_local_desc(StringInfo buf, XLogReaderState *record); +extern const char *seq_local_identify(uint8 info); +extern void seq_local_mask(char *page, BlockNumber blkno); + +/* access routines */ +extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last); +extern const char *seq_local_get_table_am(void); +extern void seq_local_init(Relation rel, int64 last_value, bool is_called); +extern void seq_local_setval(Relation rel, int64 next, bool iscalled); +extern void seq_local_reset(Relation rel, int64 startv, bool is_called, + bool reset_state); +extern void seq_local_get_state(Relation rel, int64 *last_value, + bool *is_called); +extern void seq_local_change_persistence(Relation rel, + char newrelpersistence); + +#endif /* SEQLOCALAM_H */ diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile index cd95eec37f..e5900ed77a 100644 --- a/src/backend/access/rmgrdesc/Makefile +++ b/src/backend/access/rmgrdesc/Makefile @@ -24,7 +24,7 @@ OBJS = \ relmapdesc.o \ replorigindesc.o \ rmgrdesc_utils.o \ - seqdesc.o \ + seqlocaldesc.o \ smgrdesc.o \ spgdesc.o \ standbydesc.o \ diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build index e8b7a65fc7..e390d385bf 100644 --- a/src/backend/access/rmgrdesc/meson.build +++ b/src/backend/access/rmgrdesc/meson.build @@ -17,7 +17,7 @@ rmgr_desc_sources = files( 'relmapdesc.c', 'replorigindesc.c', 'rmgrdesc_utils.c', - 'seqdesc.c', + 'seqlocaldesc.c', 'smgrdesc.c', 'spgdesc.c', 'standbydesc.c', diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/seqlocaldesc.c similarity index 64% rename from src/backend/access/rmgrdesc/seqdesc.c rename to src/backend/access/rmgrdesc/seqlocaldesc.c index cf0e02ded5..409c67335e 100644 --- a/src/backend/access/rmgrdesc/seqdesc.c +++ b/src/backend/access/rmgrdesc/seqlocaldesc.c @@ -1,44 +1,44 @@ /*------------------------------------------------------------------------- * - * seqdesc.c - * rmgr descriptor routines for commands/sequence.c + * seqlocaldesc.c + * rmgr descriptor routines for sequence/seqlocal.c * * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * src/backend/access/rmgrdesc/seqdesc.c + * src/backend/access/rmgrdesc/seqlocaldesc.c * *------------------------------------------------------------------------- */ #include "postgres.h" -#include "commands/sequence.h" +#include "access/seqlocalam.h" void -seq_desc(StringInfo buf, XLogReaderState *record) +seq_local_desc(StringInfo buf, XLogReaderState *record) { char *rec = XLogRecGetData(record); uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - xl_seq_rec *xlrec = (xl_seq_rec *) rec; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) rec; - if (info == XLOG_SEQ_LOG) + if (info == XLOG_SEQ_LOCAL_LOG) appendStringInfo(buf, "rel %u/%u/%u", xlrec->locator.spcOid, xlrec->locator.dbOid, xlrec->locator.relNumber); } const char * -seq_identify(uint8 info) +seq_local_identify(uint8 info) { const char *id = NULL; switch (info & ~XLR_INFO_MASK) { - case XLOG_SEQ_LOG: - id = "LOG"; + case XLOG_SEQ_LOCAL_LOG: + id = "SEQ_LOCAL_LOG"; break; } diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile index 9f9d31f542..a15ceec1c0 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index 1843aed38d..4d24f900b6 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -1,5 +1,7 @@ # Copyright (c) 2022-2024, PostgreSQL Global Development Group backend_sources += files( + 'seqlocalam.c', + 'seqlocalxlog.c', 'sequence.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c new file mode 100644 index 0000000000..b0a0d72966 --- /dev/null +++ b/src/backend/access/sequence/seqlocalam.c @@ -0,0 +1,634 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.c + * Local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalam.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/multixact.h" +#include "access/seqlocalam.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 SEQ_LOCAL_LOG_VALS 32 + +/* Format of tuples stored in heap table associated to local sequences */ +typedef struct FormData_pg_seq_local_data +{ + int64 last_value; + int64 log_cnt; + bool is_called; +} FormData_pg_seq_local_data; + +typedef FormData_pg_seq_local_data *Form_pg_seq_local_data; + +/* + * Columns of a local sequence relation + */ +#define SEQ_LOCAL_COL_LASTVAL 1 +#define SEQ_LOCAL_COL_LOG 2 +#define SEQ_LOCAL_COL_CALLED 3 + +#define SEQ_LOCAL_COL_FIRSTCOL SEQ_LOCAL_COL_LASTVAL +#define SEQ_LOCAL_COL_LASTCOL SEQ_LOCAL_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_LOCAL_LOG_VALS 32 + +static Form_pg_seq_local_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 tuple + * + * *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_seq_local_data +read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + seq_local_magic *sm; + Form_pg_seq_local_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SEQ_LOCAL_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_pg_seq_local_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 == RELPERSISTENCE_UNLOGGED) + { + SMgrRelation srel; + + srel = 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 content + */ +static void +fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + seq_local_magic *sm; + OffsetNumber offnum; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + sm->magic = SEQ_LOCAL_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) tuple->t_data, tuple->t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_nextval() + * + * Allocate a new value for a local sequence, based on the sequence + * configuration. + */ +int64 +seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + int64 result; + int64 fetch; + int64 next; + int64 rescnt = 0; + int64 log; + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + Page page; + bool logit = false; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + page = BufferGetPage(buf); + + *last = next = result = seq->last_value; + fetch = cache; + log = 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_LOCAL_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 = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = true; + } + else + { + XLogRecPtr redoptr = GetRedoRecPtr(); + + if (PageGetLSN(page) <= redoptr) + { + /* last update of seq was before checkpoint */ + fetch = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = 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 >= 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 = minv; + } + else + next += incby; + } + else + { + /* descending sequence */ + if ((minv < 0 && next < minv - incby) || + (minv >= 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 = maxv; + } + else + next += incby; + } + fetch--; + if (rescnt < cache) + { + log--; + rescnt++; + *last = next; + if (rescnt == 1) /* if it's first result - */ + result = next; /* it's what to return */ + } + } + + log -= fetch; /* adjust for any unfetched numbers */ + Assert(log >= 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_seq_local_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 = next; + seq->is_called = true; + seq->log_cnt = 0; + + xlrec.locator = rel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + /* Now update sequence tuple to the intended final state */ + seq->last_value = *last; /* last fetched number */ + seq->is_called = true; + seq->log_cnt = log; /* how much is logged */ + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + return result; +} + +/* + * seq_local_get_table_am() + * + * Return the table access method used by this sequence. + */ +const char * +seq_local_get_table_am(void) +{ + return "heap"; +} + +/* + * seq_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 + * the information guessed from pg_sequences. This is the first tuple + * inserted after the relation has been created, filling in its heap + * table. + */ +void +seq_local_init(Relation rel, int64 last_value, bool is_called) +{ + Datum value[SEQ_LOCAL_COL_LASTCOL]; + bool null[SEQ_LOCAL_COL_LASTCOL]; + List *elts = NIL; + List *atcmds = NIL; + ListCell *lc; + TupleDesc tupdesc; + HeapTuple tuple; + + /* + * Create relation (and fill value[] and null[] for the initial tuple). + */ + for (int i = SEQ_LOCAL_COL_FIRSTCOL; i <= SEQ_LOCAL_COL_LASTCOL; i++) + { + ColumnDef *coldef = NULL; + + switch (i) + { + case SEQ_LOCAL_COL_LASTVAL: + coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatumFast(last_value); + break; + case SEQ_LOCAL_COL_LOG: + coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatum(0); + break; + case SEQ_LOCAL_COL_CALLED: + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + value[i - 1] = BoolGetDatum(is_called); + break; + } + + coldef->is_not_null = true; + null[i - 1] = false; + elts = lappend(elts, coldef); + } + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, value, null); + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_setval() + * + * Callback for setval(). + */ +void +seq_local_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->last_value = next; /* last fetched number */ + seq->is_called = iscalled; + seq->log_cnt = 0; + + MarkBufferDirty(buf); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel)) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + Page page = BufferGetPage(buf); + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_reset() + * + * Perform a hard reset on the local sequence, rewriting its heap data + * entirely. + */ +void +seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) +{ + Form_pg_seq_local_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 = 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 = (Form_pg_seq_local_data) GETSTRUCT(tuple); + seq->last_value = startv; + seq->is_called = is_called; + if (reset_state) + seq->log_cnt = 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 == InvalidTransactionId); + Assert(rel->rd_rel->relminmxid == InvalidMultiXactId); + + /* + * Insert the modified tuple into the new storage file. + */ + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_get_state() + * + * Retrieve the state of a local sequence. + */ +void +seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + *last_value = seq->last_value; + *is_called = seq->is_called; + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_change_persistence() + * + * Persistence change for the local sequence Relation. + */ +void +seq_local_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/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c new file mode 100644 index 0000000000..db0ad969db --- /dev/null +++ b/src/backend/access/sequence/seqlocalxlog.c @@ -0,0 +1,82 @@ +/*------------------------------------------------------------------------- + * + * seqlocalxlog.c + * WAL replay logic for local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalxlog.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/bufmask.h" +#include "access/seqlocalam.h" +#include "access/xlogutils.h" +#include "storage/block.h" + +/* + * Mask a Sequence page before performing consistency checks on it. + */ +void +seq_local_mask(char *page, BlockNumber blkno) +{ + mask_page_lsn_and_checksum(page); + + mask_unused_space(page); +} + +void +seq_local_redo(XLogReaderState *record) +{ + XLogRecPtr lsn = record->EndRecPtr; + uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; + Buffer buffer; + Page page; + Page localpage; + char *item; + Size itemsz; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record); + seq_local_magic *sm; + + if (info != XLOG_SEQ_LOCAL_LOG) + elog(PANIC, "seq_redo: unknown op code %u", info); + + buffer = XLogInitBufferForRedo(record, 0); + page = (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 = (Page) palloc(BufferGetPageSize(buffer)); + + PageInit(localpage, BufferGetPageSize(buffer), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(localpage); + sm->magic = SEQ_LOCAL_MAGIC; + + item = (char *) xlrec + sizeof(xl_seq_local_rec); + itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec); + + if (PageAddItem(localpage, (Item) item, itemsz, + FirstOffsetNumber, false, false) == InvalidOffsetNumber) + elog(PANIC, "seq_local_redo: failed to add item to page"); + + PageSetLSN(localpage, lsn); + + memcpy(page, localpage, BufferGetPageSize(buffer)); + MarkBufferDirty(buffer); + UnlockReleaseBuffer(buffer); + + pfree(localpage); +} diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c index 1b7499726e..cc92268937 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/seqlocalam.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/sequence.c index 1a4fe65dbf..e75878d8c6 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,6 +16,7 @@ #include "access/bufmask.h" #include "access/htup_details.h" +#include "access/seqlocalam.h" #include "access/multixact.h" #include "access/relation.h" #include "access/sequence.h" @@ -50,23 +51,6 @@ #include "utils/varlena.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 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 current * session. This is needed to hold onto nextval/currval state. (We can't @@ -96,13 +80,9 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */ */ static SeqTableData *last_used_seq = NULL; -static void fill_seq_with_data(Relation rel, HeapTuple tuple); -static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber 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_identity, 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 = NIL; - List *atcmds = NIL; - ListCell *lc; Datum pgs_values[Natts_pg_sequence]; bool pgs_nulls[Natts_pg_sequence]; - int i; /* * 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); - /* - * Create relation (and fill value[] and null[] for the tuple) - */ - for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) - { - ColumnDef *coldef = NULL; - - switch (i) - { - case SEQ_COL_LASTVAL: - coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(last_value); - break; - case SEQ_COL_LOG: - coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatum((int64) 0); - break; - case SEQ_COL_CALLED: - coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); - value[i - 1] = BoolGetDatum(false); - break; - } - - coldef->is_not_null = true; - null[i - 1] = false; - - elts = lappend(elts, coldef); - } - stmt->relation = seq->sequence; stmt->inhRelations = NIL; stmt->constraints = NIL; @@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) */ stmt->tableElts = NIL; + /* + * Initial relation has no attributes, these can be added later via the + * "init" AM callback. + */ + stmt->tableElts = NIL; + address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); - /* Add all the attributes to the sequence */ - foreach(lc, elts) - { - AlterTableCmd *atcmd; - - atcmd = makeNode(AlterTableCmd); - atcmd->subtype = AT_AddColumnToSequence; - atcmd->def = (Node *) lfirst(lc); - atcmds = lappend(atcmds, atcmd); - } - - /* - * No recursion needed. Note that EventTriggerAlterTableStart() should - * have been called. - */ - AlterTableInternal(RelationGetRelid(rel), atcmds, false); - CommandCounterIncrement(); - - tupDesc = RelationGetDescr(rel); - - /* now initialize the sequence's data */ - tuple = heap_form_tuple(tupDesc, value, null); - fill_seq_with_data(rel, tuple); + /* now initialize the sequence table structure and its data */ + seq_local_init(rel, last_value, is_called); /* 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); pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid)); if (!HeapTupleIsValid(pgstuple)) @@ -315,40 +240,8 @@ ResetSequence(Oid seq_relid) startv = pgsform->seqstart; ReleaseSysCache(pgstuple); - /* - * Copy the existing sequence tuple. - */ - tuple = 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 = (Form_pg_sequence_data) GETSTRUCT(tuple); - seq->last_value = startv; - seq->is_called = false; - seq->log_cnt = 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 == InvalidTransactionId); - Assert(seq_rel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - fill_seq_with_data(seq_rel, tuple); + /* Sequence state is forcibly reset here. */ + seq_local_reset(seq_rel, startv, false, true); /* 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); } -/* - * 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 == RELPERSISTENCE_UNLOGGED) - { - SMgrRelation srel; - - srel = 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 content - */ -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 = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, - EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); - Assert(BufferGetBlockNumber(buf) == 0); - - page = BufferGetPage(buf); - - PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(page); - sm->magic = SEQ_MAGIC; - - /* Now insert sequence tuple */ - - /* - * Since VACUUM does not process sequences, we have to force the tuple to - * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, - InvalidOffsetNumber, false, false); - if (offnum != FirstOffsetNumber) - elog(ERROR, "failed to add sequence tuple to page"); - - /* XLOG stuff */ - if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = rel->rd_locator; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) tuple->t_data, tuple->t_len); - - recptr = 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 = false; bool is_called; int64 last_value; - HeapTuple newdatatuple; /* Open and lock sequence, and check for ownership along the way. */ relid = RangeVarGetRelidExtended(stmt->sequence, @@ -507,16 +296,8 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) seqform = (Form_pg_sequence) GETSTRUCT(seqtuple); - /* 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 = heap_copytuple(&datatuple); - newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); - last_value = newdataform->last_value; - is_called = newdataform->is_called; - - UnlockReleaseBuffer(buf); + /* Read sequence data */ + seq_local_get_state(seqrel, &last_value, &is_called); /* 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(); - /* - * 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 == InvalidTransactionId); - Assert(seqrel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - newdataform->last_value = last_value; - newdataform->is_called = is_called; - if (reset_state) - newdataform->log_cnt = 0; - fill_seq_with_data(seqrel, newdatatuple); + seq_local_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) { SeqTable elm; Relation seqrel; - Buffer buf; - HeapTupleData seqdatatuple; /* * ALTER SEQUENCE acquires this lock earlier. If we're processing an @@ -596,10 +353,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - (void) read_seq_tuple(seqrel, &buf, &seqdatatuple); - RelationSetNewRelfilenumber(seqrel, newrelpersistence); - fill_seq_with_data(seqrel, &seqdatatuple); - UnlockReleaseBuffer(buf); + seq_local_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -662,24 +416,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 = 0; + int64 result; bool cycle; - bool logit = false; /* open and lock sequence */ init_sequence(relid, &elm, &seqrel); @@ -724,105 +469,9 @@ nextval_internal(Oid relid, bool check_permissions) cycle = pgsform->seqcycle; ReleaseSysCache(pgstuple); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - page = BufferGetPage(buf); - - last = next = result = seq->last_value; - fetch = cache; - log = 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 = log = fetch + SEQ_LOG_VALS; - logit = true; - } - else - { - XLogRecPtr redoptr = GetRedoRecPtr(); - - if (PageGetLSN(page) <= redoptr) - { - /* last update of seq was before checkpoint */ - fetch = log = fetch + SEQ_LOG_VALS; - logit = 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 >= 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 = minv; - } - else - next += incby; - } - else - { - /* descending sequence */ - if ((minv < 0 && next < minv - incby) || - (minv >= 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 = maxv; - } - else - next += incby; - } - fetch--; - if (rescnt < cache) - { - log--; - rescnt++; - last = next; - if (rescnt == 1) /* if it's first result - */ - result = next; /* it's what to return */ - } - } - - log -= fetch; /* adjust for any unfetched numbers */ - Assert(log >= 0); + /* retrieve next value from the access method */ + result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -832,69 +481,6 @@ nextval_internal(Oid relid, bool check_permissions) last_used_seq = elm; - /* - * 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 = next; - seq->is_called = true; - seq->log_cnt = 0; - - xlrec.locator = seqrel->rd_locator; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - /* Now update sequence tuple to the intended final state */ - seq->last_value = last; /* last fetched number */ - seq->is_called = true; - seq->log_cnt = log; /* how much is logged */ - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); - sequence_close(seqrel, NoLock); return result; @@ -984,9 +570,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, @@ -1020,9 +603,6 @@ do_setval(Oid relid, int64 next, bool iscalled) */ PreventCommandIfParallelMode("setval()"); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - if ((next < minv) || (next > maxv)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), @@ -1044,37 +624,8 @@ do_setval(Oid relid, int64 next, bool iscalled) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - /* ready to change the on-disk (or really, in-buffer) tuple */ - START_CRIT_SECTION(); - - seq->last_value = next; /* last fetched number */ - seq->is_called = iscalled; - seq->log_cnt = 0; - - MarkBufferDirty(buf); - - /* XLOG stuff */ - if (RelationNeedsWAL(seqrel)) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - Page page = BufferGetPage(buf); - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = seqrel->rd_locator; - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); + /* Call the access method callback */ + seq_local_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1215,62 +766,6 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) } -/* - * Given an opened sequence relation, lock the page buffer and find the tuple - * - * *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 = ReadBuffer(rel, 0); - LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); - - page = BufferGetPage(*buf); - sm = (sequence_magic *) PageGetSpecialPointer(page); - - if (sm->magic != SEQ_MAGIC) - elog(ERROR, "bad magic number in sequence \"%s\": %08X", - RelationGetRelationName(rel), sm->magic); - - lp = PageGetItemId(page, FirstOffsetNumber); - Assert(ItemIdIsNormal(lp)); - - /* Note we currently only bother to set these two fields of *seqdatatuple */ - seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); - seqdatatuple->t_len = 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) != InvalidTransactionId) - { - HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); - seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; - seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; - MarkBufferDirtyHint(*buf, true); - } - - seq = (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 @@ -1852,16 +1347,13 @@ pg_sequence_read_tuple(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; + bool is_called; + int64 last_value; - seq = read_seq_tuple(seqrel, &buf, &seqtuple); + seq_local_get_state(seqrel, &last_value, &is_called); - values[0] = Int64GetDatum(seq->last_value); - values[1] = BoolGetDatum(seq->is_called); - - UnlockReleaseBuffer(buf); + values[0] = Int64GetDatum(last_value); + values[1] = BoolGetDatum(is_called); } else memset(isnull, true, sizeof(isnull)); @@ -1905,17 +1397,9 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; - - seq = read_seq_tuple(seqrel, &buf, &seqtuple); - - is_called = seq->is_called; - result = seq->last_value; - - UnlockReleaseBuffer(buf); + seq_local_get_state(seqrel, &result, &is_called); } + sequence_close(seqrel, NoLock); if (is_called) @@ -1924,57 +1408,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - -void -seq_redo(XLogReaderState *record) -{ - XLogRecPtr lsn = record->EndRecPtr; - uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - Buffer buffer; - Page page; - Page localpage; - char *item; - Size itemsz; - xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record); - sequence_magic *sm; - - if (info != XLOG_SEQ_LOG) - elog(PANIC, "seq_redo: unknown op code %u", info); - - buffer = XLogInitBufferForRedo(record, 0); - page = (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 = (Page) palloc(BufferGetPageSize(buffer)); - - PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(localpage); - sm->magic = SEQ_MAGIC; - - item = (char *) xlrec + sizeof(xl_seq_rec); - itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec); - - if (PageAddItem(localpage, (Item) item, itemsz, - FirstOffsetNumber, false, false) == 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. */ @@ -1989,14 +1422,3 @@ ResetSequenceCaches(void) last_used_seq = 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..8d1195de26 100644 --- a/src/bin/pg_waldump/.gitignore +++ b/src/bin/pg_waldump/.gitignore @@ -10,13 +10,13 @@ /gistdesc.c /hashdesc.c /heapdesc.c +/seqlocaldesc.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..2fccc7a4e2 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/seqlocalam.h" #include "access/multixact.h" #include "access/nbtxlog.h" #include "access/rmgr.h" diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl index 578e473139..0b911048e1 100644 --- a/src/bin/pg_waldump/t/001_basic.pl +++ b/src/bin/pg_waldump/t/001_basic.pl @@ -67,7 +67,7 @@ Btree Hash Gin Gist -Sequence +SequenceLocal SPGist BRIN CommitTs -- 2.45.2 [text/x-diff] v7-0005-Sequence-access-methods-backend-support.patch (61.3K, ../../[email protected]/6-v7-0005-Sequence-access-methods-backend-support.patch) download | inline diff: From 443f70107bb0b8e4657558637d0cca761422337b Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Thu, 25 Apr 2024 17:15:20 +0900 Subject: [PATCH v7 5/8] Sequence access methods - backend support The "seqlocal" sequence AM is now plugged in as a handler in the relcache, and a set of callbacks in sequenceam.h. --- src/include/access/seqlocalam.h | 15 -- src/include/access/sequenceam.h | 181 ++++++++++++++++++ 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/meson.build | 1 + src/backend/access/sequence/seqlocalam.c | 41 +++- 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 | 23 +-- 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/psql/describe.c | 2 + src/bin/psql/tab-complete.c | 4 +- src/test/regress/expected/create_am.out | 55 ++++-- src/test/regress/expected/opr_sanity.out | 12 ++ src/test/regress/expected/psql.out | 96 +++++----- src/test/regress/sql/create_am.sql | 24 ++- src/test/regress/sql/opr_sanity.sql | 10 + src/tools/pgindent/typedefs.list | 5 +- 38 files changed, 683 insertions(+), 170 deletions(-) create mode 100644 src/include/access/sequenceam.h create mode 100644 src/backend/access/sequence/sequenceamapi.c diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h index 225fb9a2cb..21936511ac 100644 --- a/src/include/access/seqlocalam.h +++ b/src/include/access/seqlocalam.h @@ -15,7 +15,6 @@ #include "access/xlogreader.h" #include "storage/relfilelocator.h" -#include "utils/rel.h" /* XLOG stuff */ #define XLOG_SEQ_LOCAL_LOG 0x00 @@ -41,18 +40,4 @@ extern void seq_local_desc(StringInfo buf, XLogReaderState *record); extern const char *seq_local_identify(uint8 info); extern void seq_local_mask(char *page, BlockNumber blkno); -/* access routines */ -extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last); -extern const char *seq_local_get_table_am(void); -extern void seq_local_init(Relation rel, int64 last_value, bool is_called); -extern void seq_local_setval(Relation rel, int64 next, bool iscalled); -extern void seq_local_reset(Relation rel, int64 startv, bool is_called, - bool reset_state); -extern void seq_local_get_state(Relation rel, int64 *last_value, - bool *is_called); -extern void seq_local_change_persistence(Relation rel, - char newrelpersistence); - #endif /* SEQLOCALAM_H */ diff --git a/src/include/access/sequenceam.h b/src/include/access/sequenceam.h new file mode 100644 index 0000000000..ac48c8b468 --- /dev/null +++ b/src/include/access/sequenceam.h @@ -0,0 +1,181 @@ +/*------------------------------------------------------------------------- + * + * sequenceam.h + * POSTGRES sequence access method definitions. + * + * + * Portions Copyright (c) 1996-2024, 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 "seqlocal" + +/* 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 value + * allocated. The result returned is the next value of the sequence computed. + */ +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); + +#endif /* SEQUENCEAM_H */ diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat index db87490282..d991a77604 100644 --- a/src/include/catalog/pg_am.dat +++ b/src/include/catalog/pg_am.dat @@ -15,6 +15,9 @@ { oid => '2', oid_symbol => 'HEAP_TABLE_AM_OID', descr => 'heap table access method', amname => 'heap', amhandler => 'heap_tableam_handler', amtype => 't' }, +{ oid => '8048', oid_symbol => 'LOCAL_SEQUENCE_AM_OID', + descr => 'local sequence access method', + amname => 'seqlocal', amhandler => 'seq_local_sequenceam_handler', amtype => 's' }, { oid => '403', oid_symbol => 'BTREE_AM_OID', descr => 'b-tree index access method', amname => 'btree', amhandler => 'bthandler', amtype => '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 */ #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) == RELKIND_TOASTVALUE || \ (relkind) == RELKIND_MATVIEW) +/* + * Relation kinds with a sequence access method (rd_sequenceam). + */ +#define RELKIND_HAS_SEQUENCE_AM(relkind) \ + ((relkind) == RELKIND_SEQUENCE) + extern int errdetail_relkind_not_supported(char relkind); #endif /* EXPOSE_TO_CLIENT_CODE */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 986df08c6e..7c4388e000 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -913,6 +913,12 @@ prorettype => 'table_am_handler', proargtypes => 'internal', prosrc => 'heap_tableam_handler' }, +# Sequence access method handlers +{ oid => '8209', descr => 'local sequence access method handler', + proname => 'seq_local_sequenceam_handler', provolatile => 'v', + prorettype => 'sequence_am_handler', proargtypes => 'internal', + prosrc => 'seq_local_sequenceam_handler' }, + # Index access method handlers { oid => '330', descr => 'btree index access method handler', proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler', @@ -7677,6 +7683,13 @@ { oid => '327', descr => 'I/O', proname => 'index_am_handler_out', prorettype => 'cstring', proargtypes => 'index_am_handler', prosrc => 'index_am_handler_out' }, +{ oid => '8207', descr => 'I/O', + proname => 'sequence_am_handler_in', proisstrict => 'f', + prorettype => 'sequence_am_handler', proargtypes => 'cstring', + prosrc => 'sequence_am_handler_in' }, +{ oid => '8208', descr => 'I/O', + proname => 'sequence_am_handler_out', prorettype => 'cstring', + proargtypes => 'sequence_am_handler', prosrc => 'sequence_am_handler_out' }, { oid => '3311', descr => 'I/O', proname => 'tsm_handler_in', proisstrict => 'f', prorettype => 'tsm_handler', proargtypes => 'cstring', prosrc => 'tsm_handler_in' }, diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat index ceff66ccde..0eba013687 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -627,6 +627,12 @@ typcategory => 'P', typinput => 'index_am_handler_in', typoutput => 'index_am_handler_out', typreceive => '-', typsend => '-', typalign => 'i' }, +{ oid => '8210', + descr => 'pseudo-type for the result of a sequence AM handler function', + typname => 'sequence_am_handler', typlen => '4', typbyval => 't', + typtype => 'p', typcategory => 'P', typinput => 'sequence_am_handler_in', + typoutput => 'sequence_am_handler_out', typreceive => '-', typsend => '-', + typalign => 'i' }, { oid => '3310', descr => 'pseudo-type for the result of a tablesample method function', typname => 'tsm_handler', typlen => '4', typbyval => 't', typtype => 'p', diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 29c511e319..39d2cae422 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -141,6 +141,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); diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.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" -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); -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 = [ '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 f5fe0d2257..6dc56e3eaf 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3112,6 +3112,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; diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 5813dba0a2..f4a49969b8 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, 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 **extra, + 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; + /* + * 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/sequence/Makefile index a15ceec1c0..62006165a1 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = seqlocalam.o seqlocalxlog.o sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o sequenceamapi.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index 4d24f900b6..ea91990552 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -4,4 +4,5 @@ backend_sources += files( 'seqlocalam.c', 'seqlocalxlog.c', 'sequence.c', + 'sequenceamapi.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c index b0a0d72966..2e524c9e99 100644 --- a/src/backend/access/sequence/seqlocalam.c +++ b/src/backend/access/sequence/seqlocalam.c @@ -17,6 +17,7 @@ #include "access/multixact.h" #include "access/seqlocalam.h" +#include "access/sequenceam.h" #include "access/xact.h" #include "access/xloginsert.h" #include "access/xlogutils.h" @@ -24,6 +25,7 @@ #include "commands/tablecmds.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "utils/builtins.h" /* @@ -230,10 +232,10 @@ fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) * Allocate a new value for a local sequence, based on the sequence * configuration. */ -int64 +static int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last) + int64 minv, int64 cache, bool cycle, + int64 *last) { int64 result; int64 fetch; @@ -417,7 +419,7 @@ seq_local_nextval(Relation rel, int64 incby, int64 maxv, * * Return the table access method used by this sequence. */ -const char * +static const char * seq_local_get_table_am(void) { return "heap"; @@ -432,7 +434,7 @@ seq_local_get_table_am(void) * inserted after the relation has been created, filling in its heap * table. */ -void +static void seq_local_init(Relation rel, int64 last_value, bool is_called) { Datum value[SEQ_LOCAL_COL_LASTCOL]; @@ -499,7 +501,7 @@ seq_local_init(Relation rel, int64 last_value, bool is_called) * * Callback for setval(). */ -void +static void seq_local_setval(Relation rel, int64 next, bool iscalled) { Buffer buf; @@ -547,7 +549,7 @@ seq_local_setval(Relation rel, int64 next, bool iscalled) * Perform a hard reset on the local sequence, rewriting its heap data * entirely. */ -void +static void seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) { Form_pg_seq_local_data seq; @@ -600,7 +602,7 @@ seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) * * Retrieve the state of a local sequence. */ -void +static void seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) { Buffer buf; @@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) * * Persistence change for the local sequence Relation. */ -void +static void seq_local_change_persistence(Relation rel, char newrelpersistence) { Buffer buf; @@ -632,3 +634,24 @@ seq_local_change_persistence(Relation rel, char newrelpersistence) fill_seq_with_data(rel, &seqdatatuple); UnlockReleaseBuffer(buf); } + +/* ------------------------------------------------------------------------ + * Definition of the local sequence access method. + * ------------------------------------------------------------------------ + */ +static const SequenceAmRoutine seq_local_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = seq_local_get_table_am, + .init = seq_local_init, + .nextval = seq_local_nextval, + .setval = seq_local_setval, + .reset = seq_local_reset, + .get_state = seq_local_get_state, + .change_persistence = seq_local_change_persistence +}; + +Datum +seq_local_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&seq_local_methods); +} diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/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 sequences - * (in contrast to other relation types like indexes). + * (in contrast to other relation types like indexes) that are independent + * of individual sequence access methods. * *------------------------------------------------------------------------- */ diff --git a/src/backend/access/sequence/sequenceamapi.c b/src/backend/access/sequence/sequenceamapi.c new file mode 100644 index 0000000000..dd1a60d827 --- /dev/null +++ b/src/backend/access/sequence/sequenceamapi.c @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------- + * + * sequenceamapi.c + * general sequence access method routines + * + * Portions Copyright (c) 1996-2024, 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 = 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 = OidFunctionCall0(amhandler); + routine = (SequenceAmRoutine *) DatumGetPointer(datum); + + if (routine == NULL || !IsA(routine, SequenceAmRoutine)) + elog(ERROR, "sequence access method handler %u did not return a SequenceAmRoutine 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 != NULL); + Assert(routine->init != NULL); + Assert(routine->nextval != NULL); + Assert(routine->setval != NULL); + Assert(routine->reset != NULL); + Assert(routine->get_state != NULL); + Assert(routine->change_persistence != 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 = SearchSysCache1(AMOID, + ObjectIdGetDatum(amoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", amoid); + aform = (Form_pg_am) GETSTRUCT(tuple); + Assert(aform->amtype == AMTYPE_SEQUENCE); + amhandleroid = 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 == '\0') + { + GUC_check_errdetail("%s cannot be empty.", + "default_sequence_access_method"); + return false; + } + + if (strlen(*newval) >= 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 != InvalidOid) + { + if (!OidIsValid(get_sequence_am_oid(*newval, true))) + { + /* + * When source == 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 == 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 01b43cc6a8..3b8d5a9989 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -1472,8 +1472,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 != RELKIND_TOASTVALUE) + if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != 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 @@ #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); } +/* + * 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 = INDEX_AM_HANDLEROID; break; + case AMTYPE_SEQUENCE: + expectedType = SEQUENCE_AM_HANDLEROID; + break; case AMTYPE_TABLE: expectedType = TABLE_AM_HANDLEROID; break; diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index e75878d8c6..1baea307ed 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,10 +16,10 @@ #include "access/bufmask.h" #include "access/htup_details.h" -#include "access/seqlocalam.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 = NIL; stmt->constraints = NIL; stmt->options = NIL; + stmt->accessMethod = seq->accessMethod ? pstrdup(seq->accessMethod) : NULL; stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; @@ -173,7 +174,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) rel = sequence_open(seqoid, AccessExclusiveLock); /* now initialize the sequence table structure and its data */ - seq_local_init(rel, last_value, is_called); + sequence_init(rel, last_value, is_called); /* process OWNED BY if given */ if (owned_by) @@ -241,7 +242,7 @@ ResetSequence(Oid seq_relid) ReleaseSysCache(pgstuple); /* Sequence state is forcibly reset here. */ - seq_local_reset(seq_rel, startv, false, true); + sequence_reset(seq_rel, startv, false, true); /* 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 = (Form_pg_sequence) GETSTRUCT(seqtuple); /* Read sequence data */ - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); /* 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(); - seq_local_reset(seqrel, last_value, is_called, reset_state); + sequence_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -353,7 +354,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - seq_local_change_persistence(seqrel, newrelpersistence); + sequence_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -470,8 +471,8 @@ nextval_internal(Oid relid, bool check_permissions) ReleaseSysCache(pgstuple); /* retrieve next value from the access method */ - result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, - &last); + result = sequence_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -625,7 +626,7 @@ do_setval(Oid relid, int64 next, bool iscalled) GetTopTransactionId(); /* Call the access method callback */ - seq_local_setval(seqrel, next, iscalled); + sequence_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1350,7 +1351,7 @@ pg_sequence_read_tuple(PG_FUNCTION_ARGS) bool is_called; int64 last_value; - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); values[0] = Int64GetDatum(last_value); values[1] = BoolGetDatum(is_called); @@ -1397,7 +1398,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - seq_local_get_state(seqrel, &result, &is_called); + sequence_get_state(seqrel, &result, &is_called); } sequence_close(seqrel, NoLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index f6e3873b29..5541948736 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -22,6 +22,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" @@ -942,14 +943,18 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, } /* - * 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 != NULL) { - Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE); - accessMethodId = get_table_am_oid(stmt->accessMethod, false); + Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE || + RELKIND_HAS_SEQUENCE_AM(relkind)); + if (RELKIND_HAS_SEQUENCE_AM(relkind)) + accessMethodId = get_sequence_am_oid(stmt->accessMethod, false); + else + accessMethodId = get_table_am_oid(stmt->accessMethod, false); } else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE) { @@ -962,6 +967,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId)) accessMethodId = get_table_am_oid(default_table_access_method, false); } + else if (RELKIND_HAS_SEQUENCE_AM(relkind)) + { + accessMethodId = get_sequence_am_oid(default_sequence_access_method, false); + } /* * 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 = \ 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 = 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 = 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 84cef57a70..8e376d974a 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -390,6 +390,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <str> copy_file_name access_method_clause attr_name table_access_method_clause name cursor_name file_name + sequence_access_method_clause cluster_index_specification %type <list> func_name handler_name qual_Op qual_all_Op subquery_Op @@ -4831,23 +4832,26 @@ RefreshMatViewStmt: CreateSeqStmt: CREATE OptTemp SEQUENCE qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); - $4->relpersistence = $2; n->sequence = $4; n->options = $5; + n->accessMethod = $6; n->ownerId = InvalidOid; n->if_not_exists = false; $$ = (Node *) n; } | CREATE OptTemp SEQUENCE IF_P NOT EXISTS qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); $7->relpersistence = $2; n->sequence = $7; n->options = $8; + n->accessMethod = $9; n->ownerId = InvalidOid; n->if_not_exists = true; $$ = (Node *) n; @@ -4884,6 +4888,11 @@ OptParenthesizedSeqOptList: '(' SeqOptList ')' { $$ = $2; } | /*EMPTY*/ { $$ = NIL; } ; +sequence_access_method_clause: + USING name { $$ = $2; } + | /*EMPTY*/ { $$ = NULL; } + ; + SeqOptList: SeqOptElem { $$ = list_make1($1); } | SeqOptList SeqOptElem { $$ = lappend($1, $2); } ; @@ -5880,6 +5889,7 @@ CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name am_type: INDEX { $$ = AMTYPE_INDEX; } + | SEQUENCE { $$ = AMTYPE_SEQUENCE; } | TABLE { $$ = AMTYPE_TABLE; } ; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 79cad4ab30..e2fd33a530 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" @@ -461,6 +462,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, seqstmt->sequence->relpersistence = cxt->rel ? cxt->rel->rd_rel->relpersistence : cxt->relation->relpersistence; seqstmt->options = seqoptions; + seqstmt->accessMethod = NULL; /* * 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/pseudotypes.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/relcache.c index 66ed24e401..6f80d4f707 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" @@ -301,6 +303,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); @@ -1204,8 +1207,7 @@ retry: if (relation->rd_rel->relkind == RELKIND_INDEX || relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) RelationInitIndexAccessInfo(relation); - else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || - relation->rd_rel->relkind == RELKIND_SEQUENCE) + else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)) RelationInitTableAccessMethod(relation); else if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { @@ -1214,6 +1216,8 @@ retry: * inherit. */ } + else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind)) + RelationInitSequenceAccessMethod(relation); else Assert(relation->rd_rel->relam == InvalidOid); @@ -1810,17 +1814,9 @@ RelationInitTableAccessMethod(Relation relation) HeapTuple tuple; Form_pg_am aform; - if (relation->rd_rel->relkind == 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 == InvalidOid); - relation->rd_amhandler = 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. @@ -1851,6 +1847,49 @@ RelationInitTableAccessMethod(Relation relation) InitTableAmRoutine(relation); } +/* + * 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 != InvalidOid); + relation->rd_amhandler = GetSequenceAmRoutineId(relation->rd_rel->relam); + + /* + * Now we can fetch the sequence AM's API struct. + */ + relation->rd_sequenceam = GetSequenceAmRoutine(relation->rd_amhandler); + + /* + * From the sequence AM, set its expected table access method. + */ + tableam_name = sequence_get_table_am(relation); + tableam_oid = get_table_am_oid(tableam_name, false); + + tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(tableam_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", + tableam_oid); + aform = (Form_pg_am) GETSTRUCT(tuple); + tableam_handler = aform->amhandler; + ReleaseSysCache(tuple); + + relation->rd_tableam = GetTableAmRoutine(tableam_handler); +} + /* * formrdesc * @@ -3711,14 +3750,17 @@ RelationBuildLocalRelation(const char *relname, rel->rd_rel->relam = accessmtd; /* - * 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); - if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(relkind)) RelationInitTableAccessMethod(rel); + else if (relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); /* * Okay to insert into the relcache hash table. @@ -4333,13 +4375,21 @@ RelationCacheInitializePhase3(void) /* Reload tableam data if needed */ if (relation->rd_tableam == NULL && - (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE)) + (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))) { RelationInitTableAccessMethod(relation); Assert(relation->rd_tableam != NULL); restart = true; } + else if (relation->rd_sequenceam == NULL && + relation->rd_rel->relkind == RELKIND_SEQUENCE) + { + RelationInitSequenceAccessMethod(relation); + Assert(relation->rd_sequenceam != NULL); + + restart = true; + } /* Release hold on the relation */ RelationDecrementReferenceCount(relation); @@ -6326,8 +6376,10 @@ load_relcache_init_file(bool shared) nailed_rels++; /* Load table AM data */ - if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || rel->rd_rel->relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind)) RelationInitTableAccessMethod(rel); + else if (rel->rd_rel->relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); Assert(rel->rd_index == NULL); Assert(rel->rd_indextuple == NULL); @@ -6339,6 +6391,7 @@ load_relcache_init_file(bool shared) Assert(rel->rd_supportinfo == NULL); Assert(rel->rd_indoption == NULL); Assert(rel->rd_indcollation == NULL); + Assert(rel->rd_sequenceam == NULL); Assert(rel->rd_opcoptions == NULL); } diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index af227b1f24..ef768e3437 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" @@ -4185,6 +4186,17 @@ struct config_string ConfigureNamesString[] = check_default_table_access_method, NULL, NULL }, + { + {"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default sequence access method for new sequences."), + 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/utils/misc/postgresql.conf.sample index 667e0dc40a..879c03c770 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -703,6 +703,7 @@ # error #search_path = '"$user", public' # schema names #row_security = on +#default_sequence_access_method = 'seqlocal' #default_table_access_method = 'heap' #default_tablespace = '' # a tablespace name, '' uses the default #default_toast_compression = 'pglz' # 'pglz' or 'lz4' diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 7c9a1f234c..98ff40f00d 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -161,10 +161,12 @@ describeAccessMethods(const char *pattern, bool verbose) "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")); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index a7ccde6d7d..0352c73e30 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 <name> 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/expected/create_am.out index 35d4cf1d46..f1ad1c583f 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) PARTITION 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 = 'seqlocal'; 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; @@ -519,18 +517,18 @@ FROM pg_class AS pc LEFT JOIN pg_am AS pa ON (pa.oid = pc.relam) WHERE pc.relname LIKE 'tableam_%_heapx' ORDER BY 3, 1, 2; - relkind | amname | relname ----------+--------+----------------------------- - f | | tableam_fdw_heapx - r | heap2 | tableam_parted_1_heapx - r | heap | tableam_parted_2_heapx - p | | tableam_parted_heapx - S | | tableam_seq_heapx - r | heap2 | tableam_tbl_heapx - r | heap2 | tableam_tblas_heapx - m | heap2 | tableam_tblmv_heapx - r | heap2 | tableam_tblselectinto_heapx - v | | tableam_view_heapx + relkind | amname | relname +---------+----------+----------------------------- + f | | tableam_fdw_heapx + r | heap2 | tableam_parted_1_heapx + r | heap | tableam_parted_2_heapx + p | | tableam_parted_heapx + S | seqlocal | tableam_seq_heapx + r | heap2 | tableam_tbl_heapx + r | heap2 | tableam_tblas_heapx + m | heap2 | tableam_tblmv_heapx + r | heap2 | tableam_tblselectinto_heapx + v | | tableam_view_heapx (10 rows) -- don't want to keep those tables, nor the default @@ -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_dump support +-- Checks for sequence access methods +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_local_sequenceam_handler; +-- Create and use sequence +CREATE SEQUENCE test_seqam USING local2; +SELECT nextval('test_seqam'::regclass); + nextval +--------- + 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/expected/opr_sanity.out index 0d734169f1..ea1f1048ea 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1933,6 +1933,18 @@ WHERE p1.oid = a1.amhandler AND a1.amtype = 't' AND -----+--------+-----+--------- (0 rows) +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + oid | amname | oid | proname +-----+--------+-----+--------- +(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 6aeb7cb963..beb01d78a4 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -5017,31 +5017,33 @@ Indexes: -- check printing info about access methods \dA List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA * List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA h* List of access methods @@ -5066,32 +5068,34 @@ List of access methods \dA: extra argument "bar" ignored \dA+ - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ * - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ h* List of access methods diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql index 825aed325e..472289994e 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 FROM tableam_tbl_heap2; -- CREATE VIEW doesn't support USING CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2; --- 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 tableam_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; --- 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 = 'seqlocal'; 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; -- we intentionally leave the objects created above alive, to verify pg_dump support + +-- Checks for sequence access methods + +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_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 = a1.amhandler AND a1.amtype = 't' AND OR p1.pronargs != 1 OR p1.proargtypes[0] != 'internal'::regtype); +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + -- **************** pg_amop **************** -- Look for illegal values in pg_amop fields diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 9e951a9e6f..6e1911a76d 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2579,6 +2579,7 @@ SeqScanState SeqTable SeqTableData SeqType +SequenceAmRoutine SequenceItem SerCommitSeqNo SerialControl @@ -3596,6 +3597,7 @@ lineno_t list_sort_comparator local_relopt local_relopts +local_sequence_magic local_source local_ts_iter local_ts_radix_tree @@ -3878,7 +3880,6 @@ scram_state scram_state_enum security_class_t sem_t -sepgsql_context_info_t sequence_magic set_join_pathlist_hook_type set_rel_pathlist_hook_type @@ -4100,6 +4101,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 @@ -4111,7 +4113,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 -- 2.45.2 [text/x-diff] v7-0006-Sequence-access-methods-dump-restore-support.patch (21.7K, ../../[email protected]/7-v7-0006-Sequence-access-methods-dump-restore-support.patch) download | inline diff: From cad4ca7b03f1adfd6ac321a43f5f1fe0508aa921 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Mon, 26 Aug 2024 13:25:19 +0900 Subject: [PATCH v7 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 | 47 +++++++++++++++----- 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 | 49 +++++++++++++++++++++ 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, 207 insertions(+), 11 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_backup_archiver.c index 8c20c263c4..0eb5f9de3e 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -174,6 +174,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) dopt->outputSuperuser = ropt->superuser; dopt->outputCreateDB = ropt->createDB; dopt->outputNoOwner = ropt->noOwner; + dopt->outputNoSequenceAm = ropt->noSequenceAm; dopt->outputNoTableAm = ropt->noTableAm; dopt->outputNoTablespaces = ropt->noTablespace; dopt->disable_triggers = ropt->disable_triggers; @@ -1224,6 +1225,7 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId, newToc->tag = pg_strdup(opts->tag); newToc->namespace = opts->namespace ? pg_strdup(opts->namespace) : NULL; newToc->tablespace = opts->tablespace ? pg_strdup(opts->tablespace) : NULL; + newToc->sequenceam = opts->sequenceam ? pg_strdup(opts->sequenceam) : NULL; newToc->tableam = opts->tableam ? pg_strdup(opts->tableam) : NULL; newToc->relkind = opts->relkind; newToc->owner = opts->owner ? pg_strdup(opts->owner) : NULL; @@ -2379,6 +2381,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, AH->currUser = NULL; /* unknown */ AH->currSchema = NULL; /* ditto */ + AH->currSequenceAm = NULL; /* ditto */ AH->currTablespace = NULL; /* ditto */ AH->currTableAm = NULL; /* ditto */ @@ -2608,6 +2611,7 @@ WriteToc(ArchiveHandle *AH) WriteStr(AH, te->copyStmt); WriteStr(AH, te->namespace); WriteStr(AH, te->tablespace); + WriteStr(AH, te->sequenceam); WriteStr(AH, te->tableam); WriteInt(AH, te->relkind); WriteStr(AH, te->owner); @@ -2712,6 +2716,9 @@ ReadToc(ArchiveHandle *AH) if (AH->version >= K_VERS_1_10) te->tablespace = ReadStr(AH); + if (AH->version >= K_VERS_1_17) + te->sequenceam = ReadStr(AH); + if (AH->version >= K_VERS_1_14) te->tableam = ReadStr(AH); @@ -3367,6 +3374,9 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname) free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; + free(AH->currTableAm); AH->currTableAm = NULL; @@ -3529,6 +3539,57 @@ _selectTablespace(ArchiveHandle *AH, const char *tablespace) destroyPQExpBuffer(qry); } +/* + * Set the proper default_sequence_access_method value for the sequence. + */ +static void +_selectSequenceAccessMethod(ArchiveHandle *AH, const char *sequenceam) +{ + RestoreOptions *ropt = AH->public.ropt; + PQExpBuffer cmd; + const char *want, + *have; + + /* do nothing in --no-sequence-access-method mode */ + if (ropt->noSequenceAm) + return; + + have = AH->currSequenceAm; + want = sequenceam; + + if (!want) + return; + + if (have && strcmp(want, have) == 0) + return; + + cmd = createPQExpBuffer(); + appendPQExpBuffer(cmd, + "SET default_sequence_access_method = %s;", + fmtId(want)); + + if (RestoringToDB(AH)) + { + PGresult *res; + + res = PQexec(AH->connection, cmd->data); + + if (!res || PQresultStatus(res) != 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 = pg_strdup(want); +} + /* * Set the proper default_table_access_method value for the table. */ @@ -3738,6 +3799,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData) _becomeOwner(AH, te); _selectOutputSchema(AH, te->namespace); _selectTablespace(AH, te->tablespace); + _selectSequenceAccessMethod(AH, te->sequenceam); if (te->relkind != RELKIND_PARTITIONED_TABLE) _selectTableAccessMethod(AH, te->tableam); @@ -4262,6 +4324,8 @@ restore_toc_entries_prefork(ArchiveHandle *AH, TocEntry *pending_list) AH->currUser = NULL; free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; free(AH->currTablespace); AH->currTablespace = NULL; free(AH->currTableAm); @@ -5001,6 +5065,7 @@ CloneArchive(ArchiveHandle *AH) clone->connCancel = NULL; clone->currUser = NULL; clone->currSchema = NULL; + clone->currSequenceAm = NULL; clone->currTableAm = NULL; clone->currTablespace = NULL; @@ -5060,6 +5125,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_backup_archiver.h index ce5ed1dd39..cdfebcd340 100644 --- a/src/bin/pg_dump/pg_backup_archiver.h +++ b/src/bin/pg_dump/pg_backup_archiver.h @@ -71,10 +71,11 @@ #define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0) /* BLOB METADATA entries * and multiple BLOBS, * relkind */ +#define K_VERS_1_17 MAKE_ARCHIVE_VERSION(1, 17, 0) /* add sequenceam */ /* 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_VERS_REV) @@ -322,6 +323,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 */ @@ -353,6 +355,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 relkind; /* relation kind, only for TABLE tags */ char *owner; @@ -394,6 +397,7 @@ typedef struct _archiveOpts const char *tag; const char *namespace; const char *tablespace; + const char *sequenceam; const char *tableam; char relkind; const char *owner; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index b6e01d3d29..0267025d7f 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -134,6 +134,7 @@ typedef struct int64 cache; /* cache size */ int64 last_value; /* last value of sequence */ bool is_called; /* whether nextval advances before returning */ + char *seqam; /* access method of sequence */ } SequenceItem; typedef enum OidOptions @@ -477,6 +478,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}, @@ -1098,6 +1100,7 @@ main(int argc, char **argv) ropt->superuser = dopt.outputSuperuser; ropt->createDB = dopt.outputCreateDB; ropt->noOwner = dopt.outputNoOwner; + ropt->noSequenceAm = dopt.outputNoSequenceAm; ropt->noTableAm = dopt.outputNoTableAm; ropt->noTablespace = dopt.outputNoTablespaces; ropt->disable_triggers = dopt.disable_triggers; @@ -1214,6 +1217,7 @@ help(const char *progname) printf(_(" --no-publications do not dump publications\n")); printf(_(" --no-security-labels do not dump security label assignments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); + printf(_(" --no-sequence-access-method do not sequence table access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); @@ -13103,6 +13107,9 @@ dumpAccessMethod(Archive *fout, const AccessMethodInfo *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; @@ -17382,26 +17389,40 @@ collectSequences(Archive *fout) * * 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. + * + * Access methods for sequences are supported since version 18. */ if (fout->remoteVersion < 100000) return; - else if (fout->remoteVersion < 180000 || - (fout->dopt->schemaOnly && !fout->dopt->sequence_data)) + else if (fout->remoteVersion < 180000) query = "SELECT seqrelid, format_type(seqtypid, NULL), " "seqstart, seqincrement, " "seqmax, seqmin, " "seqcache, seqcycle, " - "NULL, 'f' " + "NULL, 'f', NULL " "FROM pg_catalog.pg_sequence " "ORDER BY seqrelid"; + else if (fout->dopt->schemaOnly && !fout->dopt->sequence_data) + query = "SELECT s.seqrelid, format_type(s.seqtypid, NULL), " + "s.seqstart, s.seqincrement, " + "s.seqmax, s.seqmin, " + "s.seqcache, s.seqcycle, " + "NULL, 'f', a.amname AS seqam " + "FROM pg_catalog.pg_sequence s " + "JOIN pg_class c ON (c.oid = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam) " + "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) " + query = "SELECT s.seqrelid, format_type(s.seqtypid, NULL), " + "s.seqstart, s.seqincrement, " + "s.seqmax, s.seqmin, " + "s.seqcache, s.seqcycle, " + "r.last_value, r.is_called, " + "a.amname AS seqam " + "FROM pg_catalog.pg_sequence s " + "JOIN pg_class c ON (c.oid = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam), " + "pg_sequence_read_tuple(s.seqrelid) r " "ORDER BY seqrelid;"; res = ExecuteSqlQuery(fout, query, PGRES_TUPLES_OK); @@ -17421,6 +17442,10 @@ collectSequences(Archive *fout) 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); + if (!PQgetisnull(res, i, 10)) + sequences[i].seqam = pg_strdup(PQgetvalue(res, i, 10)); + else + sequences[i].seqam = NULL; } PQclear(res); @@ -17492,6 +17517,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) 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); + seq->seqam = NULL; PQclear(res); } @@ -17613,6 +17639,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) ARCHIVE_OPTS(.tag = tbinfo->dobj.name, .namespace = tbinfo->dobj.namespace->dobj.name, .owner = tbinfo->rolname, + .sequenceam = seq->seqam, .description = "SEQUENCE", .section = SECTION_PRE_DATA, .createStmt = query->data, diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index 882dbf8e86..a35bd3a044 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 = 0; static int disable_triggers = 0; static int if_exists = 0; static int inserts = 0; +static int no_sequence_access_method = 0; static int no_table_access_method = 0; static int no_tablespaces = 0; static int use_setsessauth = 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 assignments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); printf(_(" --no-sync do not wait for changes to be written safely to disk\n")); + printf(_(" --no-sequence-access-method do not dump sequence access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c index df119591cc..081954c5ce 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 = 0; static int if_exists = 0; static int no_data_for_failed_tables = 0; + static int outputNoSequenceAm = 0; static int outputNoTableAm = 0; static int outputNoTablespaces = 0; static int use_setsessauth = 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 = disable_triggers; opts->enable_row_security = enable_row_security; opts->noDataForFailedTables = no_data_for_failed_tables; + opts->noSequenceAm = outputNoSequenceAm; opts->noTableAm = outputNoTableAm; opts->noTablespace = outputNoTablespaces; opts->use_setsessauth = 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 access methods\n")); printf(_(" --no-table-access-method do not restore table access methods\n")); printf(_(" --no-tablespaces do not restore tablespace assignments\n")); printf(_(" --section=SECTION restore named section (pre-data, 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_dump.pl index 5bcc2244d5..a6128f7ce2 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 = ( 'postgres', ], }, + no_sequence_access_method => { + dump_cmd => [ + 'pg_dump', '--no-sync', + "--file=$tempdir/no_sequence_access_method.sql", + '--no-sequence-access-method', 'postgres', + ], + }, no_table_access_method => { dump_cmd => [ 'pg_dump', '--no-sync', @@ -711,6 +718,7 @@ my %full_runs = ( no_large_objects => 1, no_owner => 1, no_privs => 1, + no_sequence_access_method => 1, no_table_access_method => 1, pg_dumpall_dbprivs => 1, pg_dumpall_exclude => 1, @@ -4488,6 +4496,18 @@ my %tests = ( }, }, + 'CREATE ACCESS METHOD regress_test_sequence_am' => { + create_order => 11, + create_sql => + 'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;', + regexp => qr/^ + \QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;\E + \n/xm, + like => { + %full_runs, section_pre_data => 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 @@ -4516,6 +4536,35 @@ my %tests = ( }, }, + + # 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' => { + create_order => 12, + create_sql => ' + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_0 USING seqlocal; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_1 USING regress_sequence_am; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_2 USING seqlocal;', + regexp => qr/^ + \QSET default_sequence_access_method = 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 => { + %full_runs, %dump_test_schema_runs, section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + no_sequence_access_method => 1, + only_dump_measurement => 1, + }, + }, + 'CREATE MATERIALIZED VIEW regress_pg_dump_matview_am' => { create_order => 13, create_sql => ' diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index ffc29b04fb..0c392a8080 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -1121,6 +1121,23 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + + <para> + This option is ignored when emitting an archive (non-text) output + file. For the archive formats, you can specify the option when you + call <command>pg_restore</command>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml 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 <replaceable class="parameter">PATTERN</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml index b8b27e1719..922f69eb4a 100644 --- a/doc/src/sgml/ref/pg_restore.sgml +++ b/doc/src/sgml/ref/pg_restore.sgml @@ -733,6 +733,17 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> -- 2.45.2 [text/x-diff] v7-0007-Sequence-access-methods-core-documentation.patch (9.5K, ../../[email protected]/8-v7-0007-Sequence-access-methods-core-documentation.patch) download | inline diff: From 2c73ccd10c7c2582bbc3a29a8a0b150db9e15819 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:55:56 +0900 Subject: [PATCH v7 7/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 2937384b00..38ecfd69c1 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8997,6 +8997,22 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-default-sequence-access-method" xreflabel="default_sequence_access_method"> + <term><varname>default_sequence_access_method</varname> (<type>string</type>) + <indexterm> + <primary><varname>default_sequence_access_method</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + This parameter specifies the default sequence access method to use when + creating sequences if the <command>CREATE SEQUENCE</command> + command does not explicitly specify an access method. The default is + <literal>local</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-default-tablespace" xreflabel="default_tablespace"> <term><varname>default_tablespace</varname> (<type>string</type>) <indexterm> diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index a7ff5f8264..07aa3f18c7 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -95,6 +95,7 @@ <!ENTITY planstats SYSTEM "planstats.sgml"> <!ENTITY tableam SYSTEM "tableam.sgml"> <!ENTITY indexam SYSTEM "indexam.sgml"> +<!ENTITY sequenceam SYSTEM "sequenceam.sgml"> <!ENTITY nls SYSTEM "nls.sgml"> <!ENTITY plhandler SYSTEM "plhandler.sgml"> <!ENTITY fdwhandler SYSTEM "fdwhandler.sgml"> 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 <replaceable class="parameter">name</replaceable> <listitem> <para> This clause specifies the type of access method to define. - Only <literal>TABLE</literal> and <literal>INDEX</literal> - are supported at present. + Only <literal>TABLE</literal>, <literal>SEQUENCE</literal> and + <literal>INDEX</literal> are supported at present. </para> </listitem> </varlistentry> @@ -77,12 +77,15 @@ CREATE ACCESS METHOD <replaceable class="parameter">name</replaceable> declared to take a single argument of type <type>internal</type>, and its return type depends on the type of access method; for <literal>TABLE</literal> access methods, it must - be <type>table_am_handler</type> and for <literal>INDEX</literal> - access methods, it must be <type>index_am_handler</type>. + be <type>table_am_handler</type>; for <literal>INDEX</literal> + access methods, it must be <type>index_am_handler</type>; + for <literal>SEQUENCE</literal>, it must be + <type>sequence_am_handler</type>; 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 <xref linkend="tableam"/> and the index access method - API is described in <xref linkend="indexam"/>. + is described in <xref linkend="tableam"/>, the index access method + API is described in <xref linkend="indexam"/> and the sequence access + method is described in <xref linkend="sequenceam"/>. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/create_sequence.sgml index 1e283f13d1..52c6096e4b 100644 --- a/doc/src/sgml/ref/create_sequence.sgml +++ b/doc/src/sgml/ref/create_sequence.sgml @@ -29,6 +29,7 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceab [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ] + [ USING <replaceable class="parameter">access_method</replaceable> ] </synopsis> </refsynopsisdiv> @@ -263,6 +264,17 @@ SELECT * FROM <replaceable>name</replaceable>; </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>USING</literal> <replaceable class="parameter">access_method</replaceable></term> + <listitem> + <para> + The <literal>USING</literal> option specifies which sequence access + method will be used when generating the sequence numbers. The default + is <literal>local</literal>. + </para> + </listitem> + </varlistentry> </variablelist> </refsect1> 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 @@ +<!-- doc/src/sgml/sequenceam.sgml --> + +<chapter id="sequenceam"> + <title>Sequence Access Method Interface Definition</title> + + <indexterm> + <primary>Sequence Access Method</primary> + </indexterm> + <indexterm> + <primary>sequenceam</primary> + <secondary>Sequence Access Method</secondary> + </indexterm> + + <para> + This chapter explains the interface between the core + <productname>PostgreSQL</productname> system and <firstterm>sequence access + methods</firstterm>, 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. + </para> + + <para> + Each sequence access method is described by a row in the + <link linkend="catalog-pg-am"><structname>pg_am</structname></link> system + catalog. The <structname>pg_am</structname> entry specifies a name and a + <firstterm>handler function</firstterm> for the sequence access method. These + entries can be created and deleted using the + <xref linkend="sql-create-access-method"/> and + <xref linkend="sql-drop-access-method"/> SQL commands. + </para> + + <para> + A sequence access method handler function must be declared to accept a single + argument of type <type>internal</type> and to return the pseudo-type + <type>sequence_am_handler</type>. The argument is a dummy value that simply + 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 + <structname>SequenceAmRoutine</structname>, which contains everything that the + core code needs to know to make use of the sequence access method. The return + value needs to be of server lifetime, which is typically achieved by + defining it as a <literal>static const</literal> variable in global + scope. The <structname>SequenceAmRoutine</structname> struct, also called the + access method's <firstterm>API struct</firstterm>, 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 + <structname>SequenceAmRoutine</structname> structure (with comments inside + 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 <ulink url="https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/include/access/sequenceam.h;hb=HEAD"> + <filename>src/include/access/sequenceam.h</filename></ulink> file. + </para> + + <para> + Currently, the way a sequence access method stores data is fairly + unconstrained, and it is possible to use a predefined + <link linkend="tableam">Table Access Method</link> to store sequence + data. + </para> + + <para> + For crash safety, a sequence access method can use + <link linkend="wal"><acronym>WAL</acronym></link>, or a custom + implementation. + If <acronym>WAL</acronym> is chosen, either + <link linkend="generic-wal">Generic WAL Records</link> can be used, or a + <link linkend="custom-rmgr">Custom WAL Resource Manager</link> can be + implemented. + </para> + + <para> + Any developer of a new <literal>sequence access method</literal> can refer to + the existing <literal>local</literal> implementation present in + <filename>src/backend/access/sequence/local.c</filename> for details of + its implementation. + </para> +</chapter> -- 2.45.2 [text/x-diff] v7-0008-snowflake-Add-sequence-AM-based-on-it.patch (28.0K, ../../[email protected]/9-v7-0008-snowflake-Add-sequence-AM-based-on-it.patch) download | inline diff: From 54977af70003644e8b396ea1c5a6f9bb5c74c660 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 26 Apr 2024 15:09:00 +0900 Subject: [PATCH v7 8/8] snowflake: Add sequence AM based on it This includes documentation and a basic implementation, though this is in a very early stage. --- doc/src/sgml/contrib.sgml | 1 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/snowflake.sgml | 100 ++++ contrib/Makefile | 1 + contrib/meson.build | 1 + contrib/snowflake/.gitignore | 3 + contrib/snowflake/Makefile | 19 + contrib/snowflake/expected/snowflake.out | 73 +++ contrib/snowflake/meson.build | 34 ++ contrib/snowflake/snowflake--1.0.sql | 21 + contrib/snowflake/snowflake.c | 570 +++++++++++++++++++++++ contrib/snowflake/snowflake.control | 5 + contrib/snowflake/sql/snowflake.sql | 29 ++ 13 files changed, 858 insertions(+) create mode 100644 doc/src/sgml/snowflake.sgml create mode 100644 contrib/snowflake/.gitignore create mode 100644 contrib/snowflake/Makefile create mode 100644 contrib/snowflake/expected/snowflake.out create mode 100644 contrib/snowflake/meson.build create mode 100644 contrib/snowflake/snowflake--1.0.sql create mode 100644 contrib/snowflake/snowflake.c create mode 100644 contrib/snowflake/snowflake.control create mode 100644 contrib/snowflake/sql/snowflake.sql diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml index 44639a8dca..e7b8871e8a 100644 --- a/doc/src/sgml/contrib.sgml +++ b/doc/src/sgml/contrib.sgml @@ -166,6 +166,7 @@ CREATE EXTENSION <replaceable>extension_name</replaceable>; &seg; &sepgsql; &contrib-spi; + &snowflake; &sslinfo; &tablefunc; &tcn; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 07aa3f18c7..496ac19f25 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -156,6 +156,7 @@ <!ENTITY seg SYSTEM "seg.sgml"> <!ENTITY contrib-spi SYSTEM "contrib-spi.sgml"> <!ENTITY sepgsql SYSTEM "sepgsql.sgml"> +<!ENTITY snowflake SYSTEM "snowflake.sgml"> <!ENTITY sslinfo SYSTEM "sslinfo.sgml"> <!ENTITY tablefunc SYSTEM "tablefunc.sgml"> <!ENTITY tcn SYSTEM "tcn.sgml"> diff --git a/doc/src/sgml/snowflake.sgml b/doc/src/sgml/snowflake.sgml new file mode 100644 index 0000000000..060699e7ec --- /dev/null +++ b/doc/src/sgml/snowflake.sgml @@ -0,0 +1,100 @@ +<!-- doc/src/sgml/snowflake.sgml --> + +<sect1 id="snowflake" xreflabel="snowflake"> + <title>snowflake — sequence access method</title> + + <indexterm zone="snowflake"> + <primary>snowflake</primary> + </indexterm> + + <para> + <literal>snowflake</literal> provides a sequence access method based on + <ulink url="https://en.wikipedia.org/wiki/Snowflake_ID">Snowflake IDs</ulink>. + </para> + + <para> + A Snowflake ID (or snowflake) is a unique 64-bit identifier made of three + components: + <itemizedlist spacing="compact"> + <listitem><para>41 bits for a timestamp, epoch-adjusted in milli-seconds</para></listitem> + <listitem><para>10 bits for machine ID</para></listitem> + <listitem><para>12 bits for a sequence number</para></listitem> + </itemizedlist> + </para> + + <sect2 id="snowflake-functions"> + <title>Functions</title> + + <variablelist> + <varlistentry> + <term> + <function>snowflake_get(raw int8) returns record</function> + <indexterm> + <primary>snowflake_get</primary> + <secondary>function</secondary> + </indexterm> + </term> + + <listitem> + <para> + Returns a record made of the timestamp in milli-seconds, the machine ID + and the sequence number for a single snowflake ID. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-parameters"> + <title>Configuration Parameters</title> + + <variablelist> + <varlistentry> + <term> + <varname>snowflake.machine_id</varname> + <indexterm> + <primary><varname>snowflake.machine_id</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Machine ID assigned to the snowflake IDs used in the sequence. The + default value is <literal>1</literal>. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-examples"> + <title>Examples</title> + + <para> + This is an example of creating a snowflake sequence: + </para> + +<programlisting> +CREATE SEQUENCE snowflake_seq USING snowflake; +</programlisting> + + <para> + Similarly to the default sequence access method, snowflake sequences + can be queried as a table: + </para> + +<programlisting> + =# SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) +=# SELECT to_timestamp(time_ms / 1000), machine, counter + FROM snowflake_get(nextval('snowflake_seq')); + to_timestamp | machine | counter +------------------------+---------+--------- + 2024-04-26 14:28:26+09 | 1 | 3 +(1 row) +</programlisting> + </sect2> + +</sect1> diff --git a/contrib/Makefile b/contrib/Makefile index abd780f277..f5d1b568b2 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -42,6 +42,7 @@ SUBDIRS = \ pg_walinspect \ postgres_fdw \ seg \ + snowflake \ spi \ tablefunc \ tcn \ diff --git a/contrib/meson.build b/contrib/meson.build index 14a8906865..4f2603e7e9 100644 --- a/contrib/meson.build +++ b/contrib/meson.build @@ -57,6 +57,7 @@ subdir('pg_walinspect') subdir('postgres_fdw') subdir('seg') subdir('sepgsql') +subdir('snowflake') subdir('spi') subdir('sslinfo') # start-scripts doesn't contain build products diff --git a/contrib/snowflake/.gitignore b/contrib/snowflake/.gitignore new file mode 100644 index 0000000000..44d119cfcc --- /dev/null +++ b/contrib/snowflake/.gitignore @@ -0,0 +1,3 @@ +# Generated subdirectories +/log/ +/results/ diff --git a/contrib/snowflake/Makefile b/contrib/snowflake/Makefile new file mode 100644 index 0000000000..fa5b48d565 --- /dev/null +++ b/contrib/snowflake/Makefile @@ -0,0 +1,19 @@ +# contrib/snowflake/Makefile + +MODULES = snowflake + +EXTENSION = snowflake +DATA = snowflake--1.0.sql + +REGRESS = snowflake + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/snowflake +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/snowflake/expected/snowflake.out b/contrib/snowflake/expected/snowflake.out new file mode 100644 index 0000000000..b7e469bf73 --- /dev/null +++ b/contrib/snowflake/expected/snowflake.out @@ -0,0 +1,73 @@ +CREATE EXTENSION snowflake; +CREATE SEQUENCE snowflake_seq USING snowflake; +SET snowflake.machine_id = 2000; -- error +ERROR: 2000 is outside the valid range for parameter "snowflake.machine_id" (0 .. 1023) +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(lastval()); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 3 | t +(1 row) + +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + relpersistence +---------------- + u +(1 row) + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; + machine | counter +---------+--------- + 4 | 2 + 4 | 3 + 4 | 4 + 4 | 5 + 4 | 6 + 4 | 7 + 4 | 8 + 4 | 9 + 4 | 10 + 4 | 11 +(10 rows) + +DROP TABLE snowflake_tab; +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; diff --git a/contrib/snowflake/meson.build b/contrib/snowflake/meson.build new file mode 100644 index 0000000000..567669eea7 --- /dev/null +++ b/contrib/snowflake/meson.build @@ -0,0 +1,34 @@ +# Copyright (c) 2024, PostgreSQL Global Development Group + +snowflake_sources = files( + 'snowflake.c', +) + +if host_system == 'windows' + snowflake_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'snowflake', + '--FILEDESC', 'snowflake - sequence access method',]) +endif + +snowflake = shared_module('snowflake', + snowflake_sources, + kwargs: contrib_mod_args, +) +contrib_targets += bloom + +install_data( + 'snowflake.control', + 'snowflake--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'snowflake', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'snowflake', + ], + }, +} diff --git a/contrib/snowflake/snowflake--1.0.sql b/contrib/snowflake/snowflake--1.0.sql new file mode 100644 index 0000000000..bcb9d754f1 --- /dev/null +++ b/contrib/snowflake/snowflake--1.0.sql @@ -0,0 +1,21 @@ +/* contrib/snowflake/snowflake--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION snowflake" to load this file. \quit + +CREATE FUNCTION snowflake_sequenceam_handler(internal) + RETURNS sequence_am_handler + AS 'MODULE_PATHNAME' + LANGUAGE C; + +CREATE ACCESS METHOD snowflake + TYPE SEQUENCE HANDLER snowflake_sequenceam_handler; +COMMENT ON ACCESS METHOD snowflake IS 'snowflake sequence access method'; + +CREATE FUNCTION snowflake_get(IN raw int8, + OUT time_ms int8, + OUT machine int4, + OUT counter int4) + RETURNS record + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT diff --git a/contrib/snowflake/snowflake.c b/contrib/snowflake/snowflake.c new file mode 100644 index 0000000000..be4c4039ec --- /dev/null +++ b/contrib/snowflake/snowflake.c @@ -0,0 +1,570 @@ +/*------------------------------------------------------------------------- + * + * snowflake.c + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * contrib/snowflake/snowflake.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <sys/time.h> + +#include "access/generic_xlog.h" +#include "access/sequenceam.h" +#include "access/xact.h" +#include "catalog/storage_xlog.h" +#include "commands/tablecmds.h" +#include "fmgr.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "storage/bufmgr.h" +#include "utils/guc.h" +#include "utils/numeric.h" + +PG_MODULE_MAGIC; + +/* "special area" of a snowflake's buffer page. */ +#define SNOWFLAKE_MAGIC 0x01 + +typedef struct snowflake_magic +{ + uint32 magic; +} snowflake_magic; + +/* ----------------------------------------------------------------------- + * Snowflake ID are 64-bit based, with the following structure: + * - 41 bits for an epoch-based timestamp, in milli-seconds. + * - 10 bits for a machine ID. + * - 12 bits for a sequence counter. + * + * The timestamp can be cut to an offset. The machine ID is controlled + * by a superuser GUC. Sequence properties apply to the sequence counter, + * as the other two are environment-dependent. + * ----------------------------------------------------------------------- + */ + +/* + * Helper routines to convert a snowflake ID from/to an int64. + */ +#define SNOWFLAKE_COUNTER_MASK 0x0000000000000FFF /* 12 bits */ +#define SNOWFLAKE_COUNTER_SHIFT 0 +#define SNOWFLAKE_MACHINE_ID_MASK 0x00000000000003FF /* 10 bits */ +#define SNOWFLAKE_MACHINE_ID_SHIFT 12 /* counter */ +#define SNOWFLAKE_TIMESTAMP_MASK 0x000001FFFFFFFFFF /* 41 bits */ +#define SNOWFLAKE_TIMESTAMP_SHIFT 22 /* machine ID + counter sizes */ + +typedef struct snowflake_id +{ + uint32 count; + uint32 machine; + uint64 time_ms; +} snowflake_id; + +#define snowflake_id_to_int64(id, raw) { \ + raw = (((id).count) & SNOWFLAKE_COUNTER_MASK) << SNOWFLAKE_COUNTER_SHIFT | \ + (((id).machine) & SNOWFLAKE_MACHINE_ID_MASK) << SNOWFLAKE_MACHINE_ID_SHIFT | \ + (((id).time_ms) & SNOWFLAKE_TIMESTAMP_MASK) << SNOWFLAKE_TIMESTAMP_SHIFT; \ +} + +#define int64_to_snowflake_id(raw, id) { \ + (id).count = ((raw) >> SNOWFLAKE_COUNTER_SHIFT) & SNOWFLAKE_COUNTER_MASK; \ + (id).machine = ((raw) >> SNOWFLAKE_MACHINE_ID_SHIFT) & SNOWFLAKE_MACHINE_ID_MASK; \ + (id).time_ms = ((raw) >> SNOWFLAKE_TIMESTAMP_SHIFT) & SNOWFLAKE_TIMESTAMP_MASK; \ +} + +/* + * Format of tuples stored in heap table associated to snowflake sequence. + */ +typedef struct FormData_snowflake_data +{ + /* enough to cover 12 bits of the internal counter */ + int16 count; + bool is_called; +} FormData_snowflake_data; + +typedef FormData_snowflake_data *Form_snowflake_data; + +/* + * Columns of a snowflake sequence relation. + */ +#define SNOWFLAKE_COL_COUNT 1 +#define SNOWFLAKE_COL_CALLED 2 + +#define SNOWFLAKE_COLS 2 + +/* GUC parameter */ +static int snowflake_machine_id = 1; + +PG_FUNCTION_INFO_V1(snowflake_sequenceam_handler); + +/* ----------------------------------------------------------------------- + * Interfaces for relation manipulation. + * ----------------------------------------------------------------------- + */ + +/* + * Initialize snowflake relation's fork with some data. + */ +static void +fill_snowflake_fork(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + snowflake_magic *sm; + OffsetNumber offnum; + GenericXLogState *state = NULL; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(snowflake_magic)); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + sm->magic = SNOWFLAKE_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= HEAP_XMAX_INVALID; + ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber); + + /* + * Initialize before entering in the critical section, as this does + * allocations. + */ + if (forkNum == INIT_FORKNUM) + state = GenericXLogStart(rel); + + START_CRIT_SECTION(); + + MarkBufferDirty(buf); + + offnum = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* + * Init forks have to be logged. These go through generic WAL records + * for simplicity's sake to save from the need of a custom RMGR. + */ + if (forkNum == INIT_FORKNUM) + { + (void) GenericXLogRegisterBuffer(state, buf, GENERIC_XLOG_FULL_IMAGE); + GenericXLogFinish(state); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * Initialize snowflake relation. + * + * This needs to handle both the initial and main forks. + */ +static void +fill_snowflake(Relation rel, HeapTuple tuple) +{ + SMgrRelation srel; + + Assert(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED); + + fill_snowflake_fork(rel, tuple, MAIN_FORKNUM); + + /* init fork */ + srel = smgropen(rel->rd_locator, INVALID_PROC_NUMBER); + smgrcreate(srel, INIT_FORKNUM, false); + log_smgrcreate(&rel->rd_locator, INIT_FORKNUM); + fill_snowflake_fork(rel, tuple, INIT_FORKNUM); + FlushRelationBuffers(rel); + smgrclose(srel); +} + +/* + * Read the current state of a snowflake sequence + * + * Given an opened sequence relation, lock the page buffer and find the tuple. + * + * *buf receives the reference to the pinned-and-ex-locked buffer. + * *seqdatatuple receives the reference to the sequence tuple proper. + * + * Returns value points to the data payload of the tuple. + */ +static Form_snowflake_data +read_snowflake(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + snowflake_magic *sm; + Form_snowflake_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SNOWFLAKE_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_snowflake_data) GETSTRUCT(seqdatatuple); + + return seq; +} + + +/* ------------------------------------------------------------------------ + * Callbacks for the snowflake 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 * +snowflake_sequenceam_get_table_am(void) +{ + return "heap"; +} + +/* + * snowflake_sequenceam_init + * + * Initialize relation of a snowflake sequence. This stores the sequence + * counter in an unlogged relation as timestamps ensure value unicity. + */ +static void +snowflake_sequenceam_init(Relation rel, int64 last_value, bool is_called) +{ + Datum values[SNOWFLAKE_COLS]; + bool nulls[SNOWFLAKE_COLS]; + int16 counter; + TupleDesc tupdesc; + HeapTuple tuple; + List *elts = NIL; + ListCell *lc; + ColumnDef *coldef = NULL; + AlterTableCmd *atcmd; + List *atcmds = NIL; + + /* Adjust last_value, depending on the defaults given */ + counter = ((int16) last_value) & SNOWFLAKE_COUNTER_MASK; + + /* + * Create unlogged relation with its attributes. + */ + coldef = makeColumnDef("count", INT2OID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + + foreach(lc, elts) + { + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + /* + * Switch the relation to be unlogged. This forces a rewrite, but + * the relation is empty so that's OK. + */ + RelationSetNewRelfilenumber(rel, RELPERSISTENCE_UNLOGGED); + + /* And insert its first tuple */ + values[0] = Int16GetDatum(counter); + nulls[0] = false; + values[1] = BoolGetDatum(is_called); + nulls[1] = false; + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, values, nulls); + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_nextval + * + * Return the next value for a snowflake sequence. + */ +static int64 +snowflake_sequenceam_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + Buffer buf; + Form_snowflake_data seq; + HeapTupleData seqdatatuple; + int64 result = 0; + snowflake_id id = {0}; + struct timeval tp; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* + * The logic here is quite simple, increment the counter until its + * threshold is reached and get back to the start. If the threshold + * is reached, wait 1ms to ensure a unique timestamp. There is no + * need to do a retry as the buffer is already locked. + */ + id.count = seq->count; + id.count++; + + if (id.count > (PG_INT16_MAX & SNOWFLAKE_COUNTER_MASK)) + { + /* + * Threshold reached, so wait a bit for force clock to a new + * timestamp. + */ + id.count = 1; + pg_usleep(1000L); /* 1ms */ + } + + /* Compute timestamp, with buffer locked */ + gettimeofday(&tp, NULL); + id.time_ms = (uint64) tp.tv_sec * 1000 + + tp.tv_usec / 1000; + + /* Machine ID */ + id.machine = snowflake_machine_id; + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->count = id.count; + seq->is_called = true; + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + /* Store the last value computed for lastval() */ + snowflake_id_to_int64(id, result); + *last = result; + return result; +} + +/* + * snowflake_sequenceam_setval + * + * Set the sequence value, manipulating only the sequence counter. + */ +static void +snowflake_sequenceam_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* Change the in-buffer tuple */ + START_CRIT_SECTION(); + seq->count = (next & SNOWFLAKE_COUNTER_MASK); + seq->is_called = iscalled; + MarkBufferDirty(buf); + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_get_state + * + * Return the last sequence counter value. + */ +static void +snowflake_sequenceam_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + seq = read_snowflake(rel, &buf, &seqdatatuple); + *last_value = seq->count; + *is_called = seq->is_called; + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_reset + * + * Reset the sequence, coming down to resetting its counter. + */ +static void +snowflake_sequenceam_reset(Relation rel, int64 startv, bool is_called, + bool reset_state) +{ + HeapTupleData seqdatatuple; + HeapTuple tuple; + Form_snowflake_data seq; + Buffer buf; + + /* lock buffer page and read tuple */ + (void) read_snowflake(rel, &buf, &seqdatatuple); + + /* copy the existing tuple */ + tuple = 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 = (Form_snowflake_data) GETSTRUCT(tuple); + seq->count = (startv & SNOWFLAKE_COUNTER_MASK); + seq->is_called = is_called; + + /* create new storage */ + RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence); + + /* insert the modified tuple into the page */ + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_change_persistence + * + * There is nothing to do here, the underneath relation has to remain + * unlogged and is set as such when creating the sequence. + */ +static void +snowflake_sequenceam_change_persistence(Relation rel, char newrelpersistence) +{ + /* Nothing to do here */ +} + +/* ------------------------------------------------------------------------ + * Definition of the snowflake sequence access method. + * ------------------------------------------------------------------------ + */ + +static const SequenceAmRoutine snowflake_sequenceam_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = snowflake_sequenceam_get_table_am, + .init = snowflake_sequenceam_init, + .nextval = snowflake_sequenceam_nextval, + .setval = snowflake_sequenceam_setval, + .get_state = snowflake_sequenceam_get_state, + .reset = snowflake_sequenceam_reset, + .change_persistence = snowflake_sequenceam_change_persistence +}; + +Datum +snowflake_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&snowflake_sequenceam_methods); +} + +/* Utility functions */ + +/* + * snowflake_get + * + * Return a tuple worth of snowflake ID data, in a readable shape. + */ +PG_FUNCTION_INFO_V1(snowflake_get); +Datum +snowflake_get(PG_FUNCTION_ARGS) +{ +#define SNOWFLAKE_GET_COLS 3 + int64 raw = PG_GETARG_INT64(0); + Datum *values; + bool *nulls; + TupleDesc tupdesc; + snowflake_id id; + + /* determine result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + int64_to_snowflake_id(raw, id); + + nulls = palloc0(sizeof(bool) * tupdesc->natts); + values = palloc0(sizeof(Datum) * tupdesc->natts); + + values[0] = Int64GetDatum(id.time_ms); + values[1] = Int32GetDatum(id.machine); + values[2] = Int32GetDatum(id.count); + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Entry point when loading extension. + */ +void +_PG_init(void) +{ + DefineCustomIntVariable("snowflake.machine_id", + "Machine ID to use with snowflake sequence.", + "Default value is 1.", + &snowflake_machine_id, + 1, 0, 1023, /* 10 bits as max */ + PGC_SUSET, + 0, NULL, NULL, NULL); +} diff --git a/contrib/snowflake/snowflake.control b/contrib/snowflake/snowflake.control new file mode 100644 index 0000000000..7b8c6089c2 --- /dev/null +++ b/contrib/snowflake/snowflake.control @@ -0,0 +1,5 @@ +# snowflake extension +comment = 'snowflake - sequence access method' +default_version = '1.0' +module_pathname = '$libdir/snowflake' +relocatable = true diff --git a/contrib/snowflake/sql/snowflake.sql b/contrib/snowflake/sql/snowflake.sql new file mode 100644 index 0000000000..395d166ba4 --- /dev/null +++ b/contrib/snowflake/sql/snowflake.sql @@ -0,0 +1,29 @@ +CREATE EXTENSION snowflake; + +CREATE SEQUENCE snowflake_seq USING snowflake; + +SET snowflake.machine_id = 2000; -- error +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(lastval()); +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; +DROP TABLE snowflake_tab; + +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; -- 2.45.2 [application/pgp-signature] signature.asc (833B, ../../[email protected]/10-signature.asc) download ^ permalink raw reply [nested|flat] 14+ messages in thread
* Re: Sequence Access Methods, round two @ 2024-08-30 08:24 Michael Paquier <[email protected]> parent: Michael Paquier <[email protected]> 0 siblings, 1 reply; 14+ messages in thread From: Michael Paquier @ 2024-08-30 08:24 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; +Cc: Peter Smith <[email protected]>; Postgres hackers <[email protected]> On Mon, Aug 26, 2024 at 01:45:12PM +0900, Michael Paquier wrote: > Finally, I have been rather annoyed by the addition of log_cnt in the > new function pg_sequence_read_tuple(). This patch set could also > implement a new system function, but it looks like a waste as we don't > care about log_cnt in pg_dump and pg_upgrade on HEAD, so I'm proposing > to remove it on a different thread: > https://www.postgresql.org/message-id/Zsvka3r-y2ZoXAdH%40paquier.xyz Following a83a944e9fdd, rebased as v8. -- Michael Attachments: [text/x-diff] v8-0001-Remove-FormData_pg_sequence_data-from-init_params.patch (9.2K, ../../[email protected]/2-v8-0001-Remove-FormData_pg_sequence_data-from-init_params.patch) download | inline diff: From 95022429328d5d9eb6d80358d19cd8b0b0b0da5a Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:00:45 +0900 Subject: [PATCH v8 1/7] 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/sequence.c index b37fd688d3..e7a31418d2 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 rel, 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); 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 = makeNode(CreateStmt); @@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) /* 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); /* @@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) { case SEQ_COL_LASTVAL: coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(seqdataform.last_value); + value[i - 1] = Int64GetDatumFast(last_value); break; case SEQ_COL_LOG: coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); @@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) ObjectAddress address; Relation rel; HeapTuple seqtuple; + bool reset_state = false; + bool is_called; + int64 last_value; HeapTuple newdatatuple; /* 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 = heap_copytuple(&datatuple); newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); + last_value = newdataform->last_value; + is_called = newdataform->is_called; UnlockReleaseBuffer(buf); /* 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); /* 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 = last_value; + newdataform->is_called = is_called; + if (reset_state) + newdataform->log_cnt = 0; fill_seq_with_data(seqrel, newdatatuple); } @@ -1236,17 +1249,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). Also + * 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 overridden. * * Note: we force a sequence rewrite whenever we change parameters that affect - * 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. Currently, * 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 @@ -1257,7 +1272,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) { @@ -1360,11 +1377,11 @@ init_params(ParseState *pstate, List *options, bool for_identity, } /* - * 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 = 0; + *reset_state = true; /* AS type */ if (as_type != NULL) @@ -1413,7 +1430,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("INCREMENT must not be zero"))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1425,7 +1442,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, { seqform->seqcycle = boolVal(is_cycled->arg); Assert(BoolIsValid(seqform->seqcycle)); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1436,7 +1453,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (max_value != NULL && max_value->arg) { seqform->seqmax = defGetInt64(max_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || max_value != NULL || reset_max_value) { @@ -1452,7 +1469,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmax = -1; /* descending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate maximum value. No need to check INT8 as seqmax is an int64 */ @@ -1468,7 +1485,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (min_value != NULL && min_value->arg) { seqform->seqmin = defGetInt64(min_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || min_value != NULL || reset_min_value) { @@ -1484,7 +1501,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmin = 1; /* ascending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate minimum value. No need to check INT8 as seqmin is an int64 */ @@ -1535,30 +1552,30 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (restart_value != NULL) { if (restart_value->arg != NULL) - seqdataform->last_value = defGetInt64(restart_value); + *last_value = defGetInt64(restart_value); else - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; - seqdataform->log_cnt = 0; + *last_value = seqform->seqstart; + *is_called = false; + *reset_state = true; } else if (isInit) { - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; + *last_value = seqform->seqstart; + *is_called = false; } /* 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))); /* CACHE */ @@ -1570,7 +1587,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("CACHE (%lld) must be greater than zero", (long long) seqform->seqcache))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { -- 2.45.2 [text/x-diff] v8-0002-Integrate-addition-of-attributes-for-sequences-wi.patch (11.1K, ../../[email protected]/3-v8-0002-Integrate-addition-of-attributes-for-sequences-wi.patch) download | inline diff: From e38c73be51490212cdb44eb0ac5e7d9f598adbd9 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 19 Jan 2024 15:00:14 +0900 Subject: [PATCH v8 2/7] 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 124d853e49..f5fe0d2257 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2338,6 +2338,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/sequence.c index e7a31418d2..633af9d40f 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 = NIL; + List *atcmds = 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 = NIL; for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) { ColumnDef *coldef = NULL; @@ -198,7 +200,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) coldef->is_not_null = true; null[i - 1] = false; - stmt->tableElts = lappend(stmt->tableElts, coldef); + elts = lappend(elts, coldef); } stmt->relation = seq->sequence; @@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; + /* + * Initial relation has no attributes, these are added later. + */ + stmt->tableElts = NIL; address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + tupDesc = RelationGetDescr(rel); /* now initialize the sequence's data */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index b3cc6f8f69..700ac3679a 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4480,6 +4480,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 */ @@ -4783,6 +4784,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* Recursion occurs during execution phase */ pass = 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 = 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, @@ -5178,6 +5186,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 = ATExecAddColumn(wqueue, tab, rel, &cmd, cmd->recurse, false, @@ -6339,6 +6348,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 b2ea8125c9..6a65db0910 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1664,7 +1664,11 @@ ProcessUtilitySlow(ParseState *pstate, break; case T_CreateSeqStmt: + EventTriggerAlterTableStart(parsetree); address = DefineSequence(pstate, (CreateSeqStmt *) parsetree); + /* stashed internally */ + commandCollected = true; + EventTriggerAlterTableEnd(); break; case T_AlterSeqStmt: diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/test/modules/test_ddl_deparse/expected/alter_table.out index 6daa186a84..24b2179455 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 of sequence parent_b_seq NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type ADD COLUMN (and recurse) desc column b of table parent NOTICE: DDL test: type simple, tag ALTER SEQUENCE @@ -76,7 +79,10 @@ NOTICE: subcommand: type SET NOT NULL desc column a of table parent NOTICE: subcommand: type SET NOT NULL desc column a of table child NOTICE: subcommand: type SET NOT NULL desc column a of table grandchild 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 of 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 table parent diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out 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 of 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 2178ce83e9..2be95f99f8 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 of 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 of 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 of sequence datatype_table_is_small_seq NOTICE: DDL test: type simple, tag CREATE TABLE NOTICE: DDL test: type simple, tag CREATE INDEX NOTICE: DDL test: type simple, tag CREATE INDEX diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c index 2758ae82d7..280642e81c 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 = "ADD COLUMN"; break; + case AT_AddColumnToSequence: + strtype = "ADD COLUMN TO SEQUENCE"; + break; case AT_AddColumnToView: strtype = "ADD COLUMN TO VIEW"; break; -- 2.45.2 [text/x-diff] v8-0003-Refactor-code-for-in-core-local-sequences.patch (54.5K, ../../[email protected]/4-v8-0003-Refactor-code-for-in-core-local-sequences.patch) download | inline diff: From 29154ed1ef980ba152251f5b22cdbfa7a15c77ca Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Thu, 25 Apr 2024 16:49:47 +0900 Subject: [PATCH v8 3/7] Refactor code for in-core "local" sequences This commit restructures the code of in-core sequences into a new set of files: - seqdesc.c is renamed to seqlocaldesc.c. - seqlocalxlog.c to the code in the WAL replay logic. - seqlocalam.c to store a set of routines called from sequence.c, finishing the separation between the main sequence logic and the in-core sequences. - seqlocalam.h to store the AM-specific structures and routine related to the in-core sequences. WAL records are renamed to "SequenceLocal" with structures, variables and file structures mapping to that. --- src/include/access/rmgrlist.h | 2 +- src/include/access/seqlocalam.h | 58 ++ src/backend/access/rmgrdesc/Makefile | 2 +- src/backend/access/rmgrdesc/meson.build | 2 +- .../rmgrdesc/{seqdesc.c => seqlocaldesc.c} | 20 +- src/backend/access/sequence/Makefile | 2 +- src/backend/access/sequence/meson.build | 2 + src/backend/access/sequence/seqlocalam.c | 634 ++++++++++++++++++ src/backend/access/sequence/seqlocalxlog.c | 82 +++ src/backend/access/transam/rmgr.c | 1 + src/backend/commands/sequence.c | 634 +----------------- src/bin/pg_waldump/.gitignore | 2 +- src/bin/pg_waldump/rmgrdesc.c | 1 + src/bin/pg_waldump/t/001_basic.pl | 2 +- 14 files changed, 822 insertions(+), 622 deletions(-) create mode 100644 src/include/access/seqlocalam.h rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%) create mode 100644 src/backend/access/sequence/seqlocalam.c create mode 100644 src/backend/access/sequence/seqlocalxlog.c diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h index 78e6b908c6..fa94beb6ed 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, btree_identify, btree_xlog PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL, hash_mask, NULL) PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, 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, NULL, seq_mask, NULL) +PG_RMGR(RM_SEQ_LOCAL_ID, "SequenceLocal", seq_local_redo, seq_local_desc, seq_local_identify, NULL, NULL, seq_local_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, NULL, brin_mask, NULL) PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL, NULL, NULL) diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h new file mode 100644 index 0000000000..225fb9a2cb --- /dev/null +++ b/src/include/access/seqlocalam.h @@ -0,0 +1,58 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.h + * Local sequence access method. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/access/seqlocalam.h + * + *------------------------------------------------------------------------- + */ +#ifndef SEQLOCALAM_H +#define SEQLOCALAM_H + +#include "access/xlogreader.h" +#include "storage/relfilelocator.h" +#include "utils/rel.h" + +/* XLOG stuff */ +#define XLOG_SEQ_LOCAL_LOG 0x00 + +typedef struct xl_seq_local_rec +{ + RelFileLocator locator; + /* SEQUENCE TUPLE DATA FOLLOWS AT THE END */ +} xl_seq_local_rec; + +/* + * The "special area" of a local sequence's buffer page looks like this. + */ +#define SEQ_LOCAL_MAGIC 0x1717 + +typedef struct seq_local_magic +{ + uint32 magic; +} seq_local_magic; + +extern void seq_local_redo(XLogReaderState *record); +extern void seq_local_desc(StringInfo buf, XLogReaderState *record); +extern const char *seq_local_identify(uint8 info); +extern void seq_local_mask(char *page, BlockNumber blkno); + +/* access routines */ +extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last); +extern const char *seq_local_get_table_am(void); +extern void seq_local_init(Relation rel, int64 last_value, bool is_called); +extern void seq_local_setval(Relation rel, int64 next, bool iscalled); +extern void seq_local_reset(Relation rel, int64 startv, bool is_called, + bool reset_state); +extern void seq_local_get_state(Relation rel, int64 *last_value, + bool *is_called); +extern void seq_local_change_persistence(Relation rel, + char newrelpersistence); + +#endif /* SEQLOCALAM_H */ diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile index cd95eec37f..e5900ed77a 100644 --- a/src/backend/access/rmgrdesc/Makefile +++ b/src/backend/access/rmgrdesc/Makefile @@ -24,7 +24,7 @@ OBJS = \ relmapdesc.o \ replorigindesc.o \ rmgrdesc_utils.o \ - seqdesc.o \ + seqlocaldesc.o \ smgrdesc.o \ spgdesc.o \ standbydesc.o \ diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build index e8b7a65fc7..e390d385bf 100644 --- a/src/backend/access/rmgrdesc/meson.build +++ b/src/backend/access/rmgrdesc/meson.build @@ -17,7 +17,7 @@ rmgr_desc_sources = files( 'relmapdesc.c', 'replorigindesc.c', 'rmgrdesc_utils.c', - 'seqdesc.c', + 'seqlocaldesc.c', 'smgrdesc.c', 'spgdesc.c', 'standbydesc.c', diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/seqlocaldesc.c similarity index 64% rename from src/backend/access/rmgrdesc/seqdesc.c rename to src/backend/access/rmgrdesc/seqlocaldesc.c index cf0e02ded5..409c67335e 100644 --- a/src/backend/access/rmgrdesc/seqdesc.c +++ b/src/backend/access/rmgrdesc/seqlocaldesc.c @@ -1,44 +1,44 @@ /*------------------------------------------------------------------------- * - * seqdesc.c - * rmgr descriptor routines for commands/sequence.c + * seqlocaldesc.c + * rmgr descriptor routines for sequence/seqlocal.c * * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * src/backend/access/rmgrdesc/seqdesc.c + * src/backend/access/rmgrdesc/seqlocaldesc.c * *------------------------------------------------------------------------- */ #include "postgres.h" -#include "commands/sequence.h" +#include "access/seqlocalam.h" void -seq_desc(StringInfo buf, XLogReaderState *record) +seq_local_desc(StringInfo buf, XLogReaderState *record) { char *rec = XLogRecGetData(record); uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - xl_seq_rec *xlrec = (xl_seq_rec *) rec; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) rec; - if (info == XLOG_SEQ_LOG) + if (info == XLOG_SEQ_LOCAL_LOG) appendStringInfo(buf, "rel %u/%u/%u", xlrec->locator.spcOid, xlrec->locator.dbOid, xlrec->locator.relNumber); } const char * -seq_identify(uint8 info) +seq_local_identify(uint8 info) { const char *id = NULL; switch (info & ~XLR_INFO_MASK) { - case XLOG_SEQ_LOG: - id = "LOG"; + case XLOG_SEQ_LOCAL_LOG: + id = "SEQ_LOCAL_LOG"; break; } diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile index 9f9d31f542..a15ceec1c0 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index 1843aed38d..4d24f900b6 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -1,5 +1,7 @@ # Copyright (c) 2022-2024, PostgreSQL Global Development Group backend_sources += files( + 'seqlocalam.c', + 'seqlocalxlog.c', 'sequence.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c new file mode 100644 index 0000000000..b0a0d72966 --- /dev/null +++ b/src/backend/access/sequence/seqlocalam.c @@ -0,0 +1,634 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.c + * Local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalam.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/multixact.h" +#include "access/seqlocalam.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 SEQ_LOCAL_LOG_VALS 32 + +/* Format of tuples stored in heap table associated to local sequences */ +typedef struct FormData_pg_seq_local_data +{ + int64 last_value; + int64 log_cnt; + bool is_called; +} FormData_pg_seq_local_data; + +typedef FormData_pg_seq_local_data *Form_pg_seq_local_data; + +/* + * Columns of a local sequence relation + */ +#define SEQ_LOCAL_COL_LASTVAL 1 +#define SEQ_LOCAL_COL_LOG 2 +#define SEQ_LOCAL_COL_CALLED 3 + +#define SEQ_LOCAL_COL_FIRSTCOL SEQ_LOCAL_COL_LASTVAL +#define SEQ_LOCAL_COL_LASTCOL SEQ_LOCAL_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_LOCAL_LOG_VALS 32 + +static Form_pg_seq_local_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 tuple + * + * *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_seq_local_data +read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + seq_local_magic *sm; + Form_pg_seq_local_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SEQ_LOCAL_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_pg_seq_local_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 == RELPERSISTENCE_UNLOGGED) + { + SMgrRelation srel; + + srel = 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 content + */ +static void +fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + seq_local_magic *sm; + OffsetNumber offnum; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + sm->magic = SEQ_LOCAL_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) tuple->t_data, tuple->t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_nextval() + * + * Allocate a new value for a local sequence, based on the sequence + * configuration. + */ +int64 +seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + int64 result; + int64 fetch; + int64 next; + int64 rescnt = 0; + int64 log; + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + Page page; + bool logit = false; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + page = BufferGetPage(buf); + + *last = next = result = seq->last_value; + fetch = cache; + log = 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_LOCAL_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 = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = true; + } + else + { + XLogRecPtr redoptr = GetRedoRecPtr(); + + if (PageGetLSN(page) <= redoptr) + { + /* last update of seq was before checkpoint */ + fetch = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = 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 >= 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 = minv; + } + else + next += incby; + } + else + { + /* descending sequence */ + if ((minv < 0 && next < minv - incby) || + (minv >= 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 = maxv; + } + else + next += incby; + } + fetch--; + if (rescnt < cache) + { + log--; + rescnt++; + *last = next; + if (rescnt == 1) /* if it's first result - */ + result = next; /* it's what to return */ + } + } + + log -= fetch; /* adjust for any unfetched numbers */ + Assert(log >= 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_seq_local_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 = next; + seq->is_called = true; + seq->log_cnt = 0; + + xlrec.locator = rel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + /* Now update sequence tuple to the intended final state */ + seq->last_value = *last; /* last fetched number */ + seq->is_called = true; + seq->log_cnt = log; /* how much is logged */ + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + return result; +} + +/* + * seq_local_get_table_am() + * + * Return the table access method used by this sequence. + */ +const char * +seq_local_get_table_am(void) +{ + return "heap"; +} + +/* + * seq_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 + * the information guessed from pg_sequences. This is the first tuple + * inserted after the relation has been created, filling in its heap + * table. + */ +void +seq_local_init(Relation rel, int64 last_value, bool is_called) +{ + Datum value[SEQ_LOCAL_COL_LASTCOL]; + bool null[SEQ_LOCAL_COL_LASTCOL]; + List *elts = NIL; + List *atcmds = NIL; + ListCell *lc; + TupleDesc tupdesc; + HeapTuple tuple; + + /* + * Create relation (and fill value[] and null[] for the initial tuple). + */ + for (int i = SEQ_LOCAL_COL_FIRSTCOL; i <= SEQ_LOCAL_COL_LASTCOL; i++) + { + ColumnDef *coldef = NULL; + + switch (i) + { + case SEQ_LOCAL_COL_LASTVAL: + coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatumFast(last_value); + break; + case SEQ_LOCAL_COL_LOG: + coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatum(0); + break; + case SEQ_LOCAL_COL_CALLED: + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + value[i - 1] = BoolGetDatum(is_called); + break; + } + + coldef->is_not_null = true; + null[i - 1] = false; + elts = lappend(elts, coldef); + } + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, value, null); + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_setval() + * + * Callback for setval(). + */ +void +seq_local_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->last_value = next; /* last fetched number */ + seq->is_called = iscalled; + seq->log_cnt = 0; + + MarkBufferDirty(buf); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel)) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + Page page = BufferGetPage(buf); + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_reset() + * + * Perform a hard reset on the local sequence, rewriting its heap data + * entirely. + */ +void +seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) +{ + Form_pg_seq_local_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 = 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 = (Form_pg_seq_local_data) GETSTRUCT(tuple); + seq->last_value = startv; + seq->is_called = is_called; + if (reset_state) + seq->log_cnt = 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 == InvalidTransactionId); + Assert(rel->rd_rel->relminmxid == InvalidMultiXactId); + + /* + * Insert the modified tuple into the new storage file. + */ + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_get_state() + * + * Retrieve the state of a local sequence. + */ +void +seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + *last_value = seq->last_value; + *is_called = seq->is_called; + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_change_persistence() + * + * Persistence change for the local sequence Relation. + */ +void +seq_local_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/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c new file mode 100644 index 0000000000..db0ad969db --- /dev/null +++ b/src/backend/access/sequence/seqlocalxlog.c @@ -0,0 +1,82 @@ +/*------------------------------------------------------------------------- + * + * seqlocalxlog.c + * WAL replay logic for local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalxlog.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/bufmask.h" +#include "access/seqlocalam.h" +#include "access/xlogutils.h" +#include "storage/block.h" + +/* + * Mask a Sequence page before performing consistency checks on it. + */ +void +seq_local_mask(char *page, BlockNumber blkno) +{ + mask_page_lsn_and_checksum(page); + + mask_unused_space(page); +} + +void +seq_local_redo(XLogReaderState *record) +{ + XLogRecPtr lsn = record->EndRecPtr; + uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; + Buffer buffer; + Page page; + Page localpage; + char *item; + Size itemsz; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record); + seq_local_magic *sm; + + if (info != XLOG_SEQ_LOCAL_LOG) + elog(PANIC, "seq_redo: unknown op code %u", info); + + buffer = XLogInitBufferForRedo(record, 0); + page = (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 = (Page) palloc(BufferGetPageSize(buffer)); + + PageInit(localpage, BufferGetPageSize(buffer), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(localpage); + sm->magic = SEQ_LOCAL_MAGIC; + + item = (char *) xlrec + sizeof(xl_seq_local_rec); + itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec); + + if (PageAddItem(localpage, (Item) item, itemsz, + FirstOffsetNumber, false, false) == InvalidOffsetNumber) + elog(PANIC, "seq_local_redo: failed to add item to page"); + + PageSetLSN(localpage, lsn); + + memcpy(page, localpage, BufferGetPageSize(buffer)); + MarkBufferDirty(buffer); + UnlockReleaseBuffer(buffer); + + pfree(localpage); +} diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c index 1b7499726e..cc92268937 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/seqlocalam.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/sequence.c index 633af9d40f..4443e569a7 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,6 +16,7 @@ #include "access/bufmask.h" #include "access/htup_details.h" +#include "access/seqlocalam.h" #include "access/multixact.h" #include "access/relation.h" #include "access/sequence.h" @@ -50,23 +51,6 @@ #include "utils/varlena.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 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 current * session. This is needed to hold onto nextval/currval state. (We can't @@ -96,13 +80,9 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */ */ static SeqTableData *last_used_seq = NULL; -static void fill_seq_with_data(Relation rel, HeapTuple tuple); -static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber 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_identity, 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 = NIL; - List *atcmds = NIL; - ListCell *lc; Datum pgs_values[Natts_pg_sequence]; bool pgs_nulls[Natts_pg_sequence]; - int i; /* * 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); - /* - * Create relation (and fill value[] and null[] for the tuple) - */ - for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) - { - ColumnDef *coldef = NULL; - - switch (i) - { - case SEQ_COL_LASTVAL: - coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(last_value); - break; - case SEQ_COL_LOG: - coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatum((int64) 0); - break; - case SEQ_COL_CALLED: - coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); - value[i - 1] = BoolGetDatum(false); - break; - } - - coldef->is_not_null = true; - null[i - 1] = false; - - elts = lappend(elts, coldef); - } - stmt->relation = seq->sequence; stmt->inhRelations = NIL; stmt->constraints = NIL; @@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) */ stmt->tableElts = NIL; + /* + * Initial relation has no attributes, these can be added later via the + * "init" AM callback. + */ + stmt->tableElts = NIL; + address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); - /* Add all the attributes to the sequence */ - foreach(lc, elts) - { - AlterTableCmd *atcmd; - - atcmd = makeNode(AlterTableCmd); - atcmd->subtype = AT_AddColumnToSequence; - atcmd->def = (Node *) lfirst(lc); - atcmds = lappend(atcmds, atcmd); - } - - /* - * No recursion needed. Note that EventTriggerAlterTableStart() should - * have been called. - */ - AlterTableInternal(RelationGetRelid(rel), atcmds, false); - CommandCounterIncrement(); - - tupDesc = RelationGetDescr(rel); - - /* now initialize the sequence's data */ - tuple = heap_form_tuple(tupDesc, value, null); - fill_seq_with_data(rel, tuple); + /* now initialize the sequence table structure and its data */ + seq_local_init(rel, last_value, is_called); /* 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); pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid)); if (!HeapTupleIsValid(pgstuple)) @@ -315,40 +240,8 @@ ResetSequence(Oid seq_relid) startv = pgsform->seqstart; ReleaseSysCache(pgstuple); - /* - * Copy the existing sequence tuple. - */ - tuple = 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 = (Form_pg_sequence_data) GETSTRUCT(tuple); - seq->last_value = startv; - seq->is_called = false; - seq->log_cnt = 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 == InvalidTransactionId); - Assert(seq_rel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - fill_seq_with_data(seq_rel, tuple); + /* Sequence state is forcibly reset here. */ + seq_local_reset(seq_rel, startv, false, true); /* 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); } -/* - * 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 == RELPERSISTENCE_UNLOGGED) - { - SMgrRelation srel; - - srel = 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 content - */ -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 = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, - EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); - Assert(BufferGetBlockNumber(buf) == 0); - - page = BufferGetPage(buf); - - PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(page); - sm->magic = SEQ_MAGIC; - - /* Now insert sequence tuple */ - - /* - * Since VACUUM does not process sequences, we have to force the tuple to - * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, - InvalidOffsetNumber, false, false); - if (offnum != FirstOffsetNumber) - elog(ERROR, "failed to add sequence tuple to page"); - - /* XLOG stuff */ - if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = rel->rd_locator; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) tuple->t_data, tuple->t_len); - - recptr = 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 = false; bool is_called; int64 last_value; - HeapTuple newdatatuple; /* Open and lock sequence, and check for ownership along the way. */ relid = RangeVarGetRelidExtended(stmt->sequence, @@ -507,16 +296,8 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) seqform = (Form_pg_sequence) GETSTRUCT(seqtuple); - /* 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 = heap_copytuple(&datatuple); - newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); - last_value = newdataform->last_value; - is_called = newdataform->is_called; - - UnlockReleaseBuffer(buf); + /* Read sequence data */ + seq_local_get_state(seqrel, &last_value, &is_called); /* 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(); - /* - * 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 == InvalidTransactionId); - Assert(seqrel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - newdataform->last_value = last_value; - newdataform->is_called = is_called; - if (reset_state) - newdataform->log_cnt = 0; - fill_seq_with_data(seqrel, newdatatuple); + seq_local_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) { SeqTable elm; Relation seqrel; - Buffer buf; - HeapTupleData seqdatatuple; /* * ALTER SEQUENCE acquires this lock earlier. If we're processing an @@ -596,10 +353,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - (void) read_seq_tuple(seqrel, &buf, &seqdatatuple); - RelationSetNewRelfilenumber(seqrel, newrelpersistence); - fill_seq_with_data(seqrel, &seqdatatuple); - UnlockReleaseBuffer(buf); + seq_local_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -662,24 +416,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 = 0; + int64 result; bool cycle; - bool logit = false; /* open and lock sequence */ init_sequence(relid, &elm, &seqrel); @@ -724,105 +469,9 @@ nextval_internal(Oid relid, bool check_permissions) cycle = pgsform->seqcycle; ReleaseSysCache(pgstuple); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - page = BufferGetPage(buf); - - last = next = result = seq->last_value; - fetch = cache; - log = 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 = log = fetch + SEQ_LOG_VALS; - logit = true; - } - else - { - XLogRecPtr redoptr = GetRedoRecPtr(); - - if (PageGetLSN(page) <= redoptr) - { - /* last update of seq was before checkpoint */ - fetch = log = fetch + SEQ_LOG_VALS; - logit = 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 >= 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 = minv; - } - else - next += incby; - } - else - { - /* descending sequence */ - if ((minv < 0 && next < minv - incby) || - (minv >= 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 = maxv; - } - else - next += incby; - } - fetch--; - if (rescnt < cache) - { - log--; - rescnt++; - last = next; - if (rescnt == 1) /* if it's first result - */ - result = next; /* it's what to return */ - } - } - - log -= fetch; /* adjust for any unfetched numbers */ - Assert(log >= 0); + /* retrieve next value from the access method */ + result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -832,69 +481,6 @@ nextval_internal(Oid relid, bool check_permissions) last_used_seq = elm; - /* - * 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 = next; - seq->is_called = true; - seq->log_cnt = 0; - - xlrec.locator = seqrel->rd_locator; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - /* Now update sequence tuple to the intended final state */ - seq->last_value = last; /* last fetched number */ - seq->is_called = true; - seq->log_cnt = log; /* how much is logged */ - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); - sequence_close(seqrel, NoLock); return result; @@ -984,9 +570,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, @@ -1020,9 +603,6 @@ do_setval(Oid relid, int64 next, bool iscalled) */ PreventCommandIfParallelMode("setval()"); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - if ((next < minv) || (next > maxv)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), @@ -1044,37 +624,8 @@ do_setval(Oid relid, int64 next, bool iscalled) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - /* ready to change the on-disk (or really, in-buffer) tuple */ - START_CRIT_SECTION(); - - seq->last_value = next; /* last fetched number */ - seq->is_called = iscalled; - seq->log_cnt = 0; - - MarkBufferDirty(buf); - - /* XLOG stuff */ - if (RelationNeedsWAL(seqrel)) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - Page page = BufferGetPage(buf); - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = seqrel->rd_locator; - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); + /* Call the access method callback */ + seq_local_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1215,62 +766,6 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) } -/* - * Given an opened sequence relation, lock the page buffer and find the tuple - * - * *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 = ReadBuffer(rel, 0); - LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); - - page = BufferGetPage(*buf); - sm = (sequence_magic *) PageGetSpecialPointer(page); - - if (sm->magic != SEQ_MAGIC) - elog(ERROR, "bad magic number in sequence \"%s\": %08X", - RelationGetRelationName(rel), sm->magic); - - lp = PageGetItemId(page, FirstOffsetNumber); - Assert(ItemIdIsNormal(lp)); - - /* Note we currently only bother to set these two fields of *seqdatatuple */ - seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); - seqdatatuple->t_len = 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) != InvalidTransactionId) - { - HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); - seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; - seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; - MarkBufferDirtyHint(*buf, true); - } - - seq = (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 @@ -1852,16 +1347,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; + bool is_called; + int64 last_value; - seq = read_seq_tuple(seqrel, &buf, &seqtuple); + seq_local_get_state(seqrel, &last_value, &is_called); - values[0] = Int64GetDatum(seq->last_value); - values[1] = BoolGetDatum(seq->is_called); - - UnlockReleaseBuffer(buf); + values[0] = Int64GetDatum(last_value); + values[1] = BoolGetDatum(is_called); } else memset(isnull, true, sizeof(isnull)); @@ -1905,17 +1397,9 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; - - seq = read_seq_tuple(seqrel, &buf, &seqtuple); - - is_called = seq->is_called; - result = seq->last_value; - - UnlockReleaseBuffer(buf); + seq_local_get_state(seqrel, &result, &is_called); } + sequence_close(seqrel, NoLock); if (is_called) @@ -1924,57 +1408,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - -void -seq_redo(XLogReaderState *record) -{ - XLogRecPtr lsn = record->EndRecPtr; - uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - Buffer buffer; - Page page; - Page localpage; - char *item; - Size itemsz; - xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record); - sequence_magic *sm; - - if (info != XLOG_SEQ_LOG) - elog(PANIC, "seq_redo: unknown op code %u", info); - - buffer = XLogInitBufferForRedo(record, 0); - page = (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 = (Page) palloc(BufferGetPageSize(buffer)); - - PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(localpage); - sm->magic = SEQ_MAGIC; - - item = (char *) xlrec + sizeof(xl_seq_rec); - itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec); - - if (PageAddItem(localpage, (Item) item, itemsz, - FirstOffsetNumber, false, false) == 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. */ @@ -1989,14 +1422,3 @@ ResetSequenceCaches(void) last_used_seq = 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..8d1195de26 100644 --- a/src/bin/pg_waldump/.gitignore +++ b/src/bin/pg_waldump/.gitignore @@ -10,13 +10,13 @@ /gistdesc.c /hashdesc.c /heapdesc.c +/seqlocaldesc.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..2fccc7a4e2 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/seqlocalam.h" #include "access/multixact.h" #include "access/nbtxlog.h" #include "access/rmgr.h" diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl index 578e473139..0b911048e1 100644 --- a/src/bin/pg_waldump/t/001_basic.pl +++ b/src/bin/pg_waldump/t/001_basic.pl @@ -67,7 +67,7 @@ Btree Hash Gin Gist -Sequence +SequenceLocal SPGist BRIN CommitTs -- 2.45.2 [text/x-diff] v8-0004-Sequence-access-methods-backend-support.patch (61.3K, ../../[email protected]/5-v8-0004-Sequence-access-methods-backend-support.patch) download | inline diff: From 8f16b5140d8b95c11823b8f7f8cf1120d4eca2d4 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Thu, 25 Apr 2024 17:15:20 +0900 Subject: [PATCH v8 4/7] Sequence access methods - backend support The "seqlocal" sequence AM is now plugged in as a handler in the relcache, and a set of callbacks in sequenceam.h. --- src/include/access/seqlocalam.h | 15 -- src/include/access/sequenceam.h | 181 ++++++++++++++++++ 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/meson.build | 1 + src/backend/access/sequence/seqlocalam.c | 41 +++- 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 | 23 +-- 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/psql/describe.c | 2 + src/bin/psql/tab-complete.c | 4 +- src/test/regress/expected/create_am.out | 55 ++++-- src/test/regress/expected/opr_sanity.out | 12 ++ src/test/regress/expected/psql.out | 96 +++++----- src/test/regress/sql/create_am.sql | 24 ++- src/test/regress/sql/opr_sanity.sql | 10 + src/tools/pgindent/typedefs.list | 5 +- 38 files changed, 683 insertions(+), 170 deletions(-) create mode 100644 src/include/access/sequenceam.h create mode 100644 src/backend/access/sequence/sequenceamapi.c diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h index 225fb9a2cb..21936511ac 100644 --- a/src/include/access/seqlocalam.h +++ b/src/include/access/seqlocalam.h @@ -15,7 +15,6 @@ #include "access/xlogreader.h" #include "storage/relfilelocator.h" -#include "utils/rel.h" /* XLOG stuff */ #define XLOG_SEQ_LOCAL_LOG 0x00 @@ -41,18 +40,4 @@ extern void seq_local_desc(StringInfo buf, XLogReaderState *record); extern const char *seq_local_identify(uint8 info); extern void seq_local_mask(char *page, BlockNumber blkno); -/* access routines */ -extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last); -extern const char *seq_local_get_table_am(void); -extern void seq_local_init(Relation rel, int64 last_value, bool is_called); -extern void seq_local_setval(Relation rel, int64 next, bool iscalled); -extern void seq_local_reset(Relation rel, int64 startv, bool is_called, - bool reset_state); -extern void seq_local_get_state(Relation rel, int64 *last_value, - bool *is_called); -extern void seq_local_change_persistence(Relation rel, - char newrelpersistence); - #endif /* SEQLOCALAM_H */ diff --git a/src/include/access/sequenceam.h b/src/include/access/sequenceam.h new file mode 100644 index 0000000000..ac48c8b468 --- /dev/null +++ b/src/include/access/sequenceam.h @@ -0,0 +1,181 @@ +/*------------------------------------------------------------------------- + * + * sequenceam.h + * POSTGRES sequence access method definitions. + * + * + * Portions Copyright (c) 1996-2024, 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 "seqlocal" + +/* 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 value + * allocated. The result returned is the next value of the sequence computed. + */ +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); + +#endif /* SEQUENCEAM_H */ diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat index db87490282..d991a77604 100644 --- a/src/include/catalog/pg_am.dat +++ b/src/include/catalog/pg_am.dat @@ -15,6 +15,9 @@ { oid => '2', oid_symbol => 'HEAP_TABLE_AM_OID', descr => 'heap table access method', amname => 'heap', amhandler => 'heap_tableam_handler', amtype => 't' }, +{ oid => '8048', oid_symbol => 'LOCAL_SEQUENCE_AM_OID', + descr => 'local sequence access method', + amname => 'seqlocal', amhandler => 'seq_local_sequenceam_handler', amtype => 's' }, { oid => '403', oid_symbol => 'BTREE_AM_OID', descr => 'b-tree index access method', amname => 'btree', amhandler => 'bthandler', amtype => '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 */ #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) == RELKIND_TOASTVALUE || \ (relkind) == RELKIND_MATVIEW) +/* + * Relation kinds with a sequence access method (rd_sequenceam). + */ +#define RELKIND_HAS_SEQUENCE_AM(relkind) \ + ((relkind) == RELKIND_SEQUENCE) + extern int errdetail_relkind_not_supported(char relkind); #endif /* EXPOSE_TO_CLIENT_CODE */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 85f42be1b3..039ec73a24 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -913,6 +913,12 @@ prorettype => 'table_am_handler', proargtypes => 'internal', prosrc => 'heap_tableam_handler' }, +# Sequence access method handlers +{ oid => '8209', descr => 'local sequence access method handler', + proname => 'seq_local_sequenceam_handler', provolatile => 'v', + prorettype => 'sequence_am_handler', proargtypes => 'internal', + prosrc => 'seq_local_sequenceam_handler' }, + # Index access method handlers { oid => '330', descr => 'btree index access method handler', proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler', @@ -7677,6 +7683,13 @@ { oid => '327', descr => 'I/O', proname => 'index_am_handler_out', prorettype => 'cstring', proargtypes => 'index_am_handler', prosrc => 'index_am_handler_out' }, +{ oid => '8207', descr => 'I/O', + proname => 'sequence_am_handler_in', proisstrict => 'f', + prorettype => 'sequence_am_handler', proargtypes => 'cstring', + prosrc => 'sequence_am_handler_in' }, +{ oid => '8208', descr => 'I/O', + proname => 'sequence_am_handler_out', prorettype => 'cstring', + proargtypes => 'sequence_am_handler', prosrc => 'sequence_am_handler_out' }, { oid => '3311', descr => 'I/O', proname => 'tsm_handler_in', proisstrict => 'f', prorettype => 'tsm_handler', proargtypes => 'cstring', prosrc => 'tsm_handler_in' }, diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat index ceff66ccde..0eba013687 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -627,6 +627,12 @@ typcategory => 'P', typinput => 'index_am_handler_in', typoutput => 'index_am_handler_out', typreceive => '-', typsend => '-', typalign => 'i' }, +{ oid => '8210', + descr => 'pseudo-type for the result of a sequence AM handler function', + typname => 'sequence_am_handler', typlen => '4', typbyval => 't', + typtype => 'p', typcategory => 'P', typinput => 'sequence_am_handler_in', + typoutput => 'sequence_am_handler_out', typreceive => '-', typsend => '-', + typalign => 'i' }, { oid => '3310', descr => 'pseudo-type for the result of a tablesample method function', typname => 'tsm_handler', typlen => '4', typbyval => 't', typtype => 'p', diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 29c511e319..39d2cae422 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -141,6 +141,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); diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.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" -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); -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 = [ '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 f5fe0d2257..6dc56e3eaf 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3112,6 +3112,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; diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 5813dba0a2..f4a49969b8 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, 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 **extra, + 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; + /* + * 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/sequence/Makefile index a15ceec1c0..62006165a1 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = seqlocalam.o seqlocalxlog.o sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o sequenceamapi.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index 4d24f900b6..ea91990552 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -4,4 +4,5 @@ backend_sources += files( 'seqlocalam.c', 'seqlocalxlog.c', 'sequence.c', + 'sequenceamapi.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c index b0a0d72966..2e524c9e99 100644 --- a/src/backend/access/sequence/seqlocalam.c +++ b/src/backend/access/sequence/seqlocalam.c @@ -17,6 +17,7 @@ #include "access/multixact.h" #include "access/seqlocalam.h" +#include "access/sequenceam.h" #include "access/xact.h" #include "access/xloginsert.h" #include "access/xlogutils.h" @@ -24,6 +25,7 @@ #include "commands/tablecmds.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "utils/builtins.h" /* @@ -230,10 +232,10 @@ fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) * Allocate a new value for a local sequence, based on the sequence * configuration. */ -int64 +static int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last) + int64 minv, int64 cache, bool cycle, + int64 *last) { int64 result; int64 fetch; @@ -417,7 +419,7 @@ seq_local_nextval(Relation rel, int64 incby, int64 maxv, * * Return the table access method used by this sequence. */ -const char * +static const char * seq_local_get_table_am(void) { return "heap"; @@ -432,7 +434,7 @@ seq_local_get_table_am(void) * inserted after the relation has been created, filling in its heap * table. */ -void +static void seq_local_init(Relation rel, int64 last_value, bool is_called) { Datum value[SEQ_LOCAL_COL_LASTCOL]; @@ -499,7 +501,7 @@ seq_local_init(Relation rel, int64 last_value, bool is_called) * * Callback for setval(). */ -void +static void seq_local_setval(Relation rel, int64 next, bool iscalled) { Buffer buf; @@ -547,7 +549,7 @@ seq_local_setval(Relation rel, int64 next, bool iscalled) * Perform a hard reset on the local sequence, rewriting its heap data * entirely. */ -void +static void seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) { Form_pg_seq_local_data seq; @@ -600,7 +602,7 @@ seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) * * Retrieve the state of a local sequence. */ -void +static void seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) { Buffer buf; @@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) * * Persistence change for the local sequence Relation. */ -void +static void seq_local_change_persistence(Relation rel, char newrelpersistence) { Buffer buf; @@ -632,3 +634,24 @@ seq_local_change_persistence(Relation rel, char newrelpersistence) fill_seq_with_data(rel, &seqdatatuple); UnlockReleaseBuffer(buf); } + +/* ------------------------------------------------------------------------ + * Definition of the local sequence access method. + * ------------------------------------------------------------------------ + */ +static const SequenceAmRoutine seq_local_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = seq_local_get_table_am, + .init = seq_local_init, + .nextval = seq_local_nextval, + .setval = seq_local_setval, + .reset = seq_local_reset, + .get_state = seq_local_get_state, + .change_persistence = seq_local_change_persistence +}; + +Datum +seq_local_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&seq_local_methods); +} diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/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 sequences - * (in contrast to other relation types like indexes). + * (in contrast to other relation types like indexes) that are independent + * of individual sequence access methods. * *------------------------------------------------------------------------- */ diff --git a/src/backend/access/sequence/sequenceamapi.c b/src/backend/access/sequence/sequenceamapi.c new file mode 100644 index 0000000000..dd1a60d827 --- /dev/null +++ b/src/backend/access/sequence/sequenceamapi.c @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------- + * + * sequenceamapi.c + * general sequence access method routines + * + * Portions Copyright (c) 1996-2024, 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 = 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 = OidFunctionCall0(amhandler); + routine = (SequenceAmRoutine *) DatumGetPointer(datum); + + if (routine == NULL || !IsA(routine, SequenceAmRoutine)) + elog(ERROR, "sequence access method handler %u did not return a SequenceAmRoutine 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 != NULL); + Assert(routine->init != NULL); + Assert(routine->nextval != NULL); + Assert(routine->setval != NULL); + Assert(routine->reset != NULL); + Assert(routine->get_state != NULL); + Assert(routine->change_persistence != 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 = SearchSysCache1(AMOID, + ObjectIdGetDatum(amoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", amoid); + aform = (Form_pg_am) GETSTRUCT(tuple); + Assert(aform->amtype == AMTYPE_SEQUENCE); + amhandleroid = 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 == '\0') + { + GUC_check_errdetail("%s cannot be empty.", + "default_sequence_access_method"); + return false; + } + + if (strlen(*newval) >= 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 != InvalidOid) + { + if (!OidIsValid(get_sequence_am_oid(*newval, true))) + { + /* + * When source == 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 == 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 01b43cc6a8..3b8d5a9989 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -1472,8 +1472,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 != RELKIND_TOASTVALUE) + if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != 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 @@ #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); } +/* + * 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 = INDEX_AM_HANDLEROID; break; + case AMTYPE_SEQUENCE: + expectedType = SEQUENCE_AM_HANDLEROID; + break; case AMTYPE_TABLE: expectedType = TABLE_AM_HANDLEROID; break; diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 4443e569a7..42c9c26aa0 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,10 +16,10 @@ #include "access/bufmask.h" #include "access/htup_details.h" -#include "access/seqlocalam.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 = NIL; stmt->constraints = NIL; stmt->options = NIL; + stmt->accessMethod = seq->accessMethod ? pstrdup(seq->accessMethod) : NULL; stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; @@ -173,7 +174,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) rel = sequence_open(seqoid, AccessExclusiveLock); /* now initialize the sequence table structure and its data */ - seq_local_init(rel, last_value, is_called); + sequence_init(rel, last_value, is_called); /* process OWNED BY if given */ if (owned_by) @@ -241,7 +242,7 @@ ResetSequence(Oid seq_relid) ReleaseSysCache(pgstuple); /* Sequence state is forcibly reset here. */ - seq_local_reset(seq_rel, startv, false, true); + sequence_reset(seq_rel, startv, false, true); /* 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 = (Form_pg_sequence) GETSTRUCT(seqtuple); /* Read sequence data */ - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); /* 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(); - seq_local_reset(seqrel, last_value, is_called, reset_state); + sequence_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -353,7 +354,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - seq_local_change_persistence(seqrel, newrelpersistence); + sequence_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -470,8 +471,8 @@ nextval_internal(Oid relid, bool check_permissions) ReleaseSysCache(pgstuple); /* retrieve next value from the access method */ - result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, - &last); + result = sequence_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -625,7 +626,7 @@ do_setval(Oid relid, int64 next, bool iscalled) GetTopTransactionId(); /* Call the access method callback */ - seq_local_setval(seqrel, next, iscalled); + sequence_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1350,7 +1351,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS) bool is_called; int64 last_value; - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); values[0] = Int64GetDatum(last_value); values[1] = BoolGetDatum(is_called); @@ -1397,7 +1398,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - seq_local_get_state(seqrel, &result, &is_called); + sequence_get_state(seqrel, &result, &is_called); } sequence_close(seqrel, NoLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 700ac3679a..795b06b5ea 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -22,6 +22,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" @@ -943,14 +944,18 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, } /* - * 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 != NULL) { - Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE); - accessMethodId = get_table_am_oid(stmt->accessMethod, false); + Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE || + RELKIND_HAS_SEQUENCE_AM(relkind)); + if (RELKIND_HAS_SEQUENCE_AM(relkind)) + accessMethodId = get_sequence_am_oid(stmt->accessMethod, false); + else + accessMethodId = get_table_am_oid(stmt->accessMethod, false); } else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE) { @@ -963,6 +968,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId)) accessMethodId = get_table_am_oid(default_table_access_method, false); } + else if (RELKIND_HAS_SEQUENCE_AM(relkind)) + { + accessMethodId = get_sequence_am_oid(default_sequence_access_method, false); + } /* * 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 = \ 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 = 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 = 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 84cef57a70..8e376d974a 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -390,6 +390,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <str> copy_file_name access_method_clause attr_name table_access_method_clause name cursor_name file_name + sequence_access_method_clause cluster_index_specification %type <list> func_name handler_name qual_Op qual_all_Op subquery_Op @@ -4831,23 +4832,26 @@ RefreshMatViewStmt: CreateSeqStmt: CREATE OptTemp SEQUENCE qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); - $4->relpersistence = $2; n->sequence = $4; n->options = $5; + n->accessMethod = $6; n->ownerId = InvalidOid; n->if_not_exists = false; $$ = (Node *) n; } | CREATE OptTemp SEQUENCE IF_P NOT EXISTS qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); $7->relpersistence = $2; n->sequence = $7; n->options = $8; + n->accessMethod = $9; n->ownerId = InvalidOid; n->if_not_exists = true; $$ = (Node *) n; @@ -4884,6 +4888,11 @@ OptParenthesizedSeqOptList: '(' SeqOptList ')' { $$ = $2; } | /*EMPTY*/ { $$ = NIL; } ; +sequence_access_method_clause: + USING name { $$ = $2; } + | /*EMPTY*/ { $$ = NULL; } + ; + SeqOptList: SeqOptElem { $$ = list_make1($1); } | SeqOptList SeqOptElem { $$ = lappend($1, $2); } ; @@ -5880,6 +5889,7 @@ CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name am_type: INDEX { $$ = AMTYPE_INDEX; } + | SEQUENCE { $$ = AMTYPE_SEQUENCE; } | TABLE { $$ = AMTYPE_TABLE; } ; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 79cad4ab30..e2fd33a530 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" @@ -461,6 +462,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, seqstmt->sequence->relpersistence = cxt->rel ? cxt->rel->rd_rel->relpersistence : cxt->relation->relpersistence; seqstmt->options = seqoptions; + seqstmt->accessMethod = NULL; /* * 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/pseudotypes.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/relcache.c index 66ed24e401..6f80d4f707 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" @@ -301,6 +303,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); @@ -1204,8 +1207,7 @@ retry: if (relation->rd_rel->relkind == RELKIND_INDEX || relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) RelationInitIndexAccessInfo(relation); - else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || - relation->rd_rel->relkind == RELKIND_SEQUENCE) + else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)) RelationInitTableAccessMethod(relation); else if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { @@ -1214,6 +1216,8 @@ retry: * inherit. */ } + else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind)) + RelationInitSequenceAccessMethod(relation); else Assert(relation->rd_rel->relam == InvalidOid); @@ -1810,17 +1814,9 @@ RelationInitTableAccessMethod(Relation relation) HeapTuple tuple; Form_pg_am aform; - if (relation->rd_rel->relkind == 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 == InvalidOid); - relation->rd_amhandler = 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. @@ -1851,6 +1847,49 @@ RelationInitTableAccessMethod(Relation relation) InitTableAmRoutine(relation); } +/* + * 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 != InvalidOid); + relation->rd_amhandler = GetSequenceAmRoutineId(relation->rd_rel->relam); + + /* + * Now we can fetch the sequence AM's API struct. + */ + relation->rd_sequenceam = GetSequenceAmRoutine(relation->rd_amhandler); + + /* + * From the sequence AM, set its expected table access method. + */ + tableam_name = sequence_get_table_am(relation); + tableam_oid = get_table_am_oid(tableam_name, false); + + tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(tableam_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", + tableam_oid); + aform = (Form_pg_am) GETSTRUCT(tuple); + tableam_handler = aform->amhandler; + ReleaseSysCache(tuple); + + relation->rd_tableam = GetTableAmRoutine(tableam_handler); +} + /* * formrdesc * @@ -3711,14 +3750,17 @@ RelationBuildLocalRelation(const char *relname, rel->rd_rel->relam = accessmtd; /* - * 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); - if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(relkind)) RelationInitTableAccessMethod(rel); + else if (relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); /* * Okay to insert into the relcache hash table. @@ -4333,13 +4375,21 @@ RelationCacheInitializePhase3(void) /* Reload tableam data if needed */ if (relation->rd_tableam == NULL && - (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE)) + (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))) { RelationInitTableAccessMethod(relation); Assert(relation->rd_tableam != NULL); restart = true; } + else if (relation->rd_sequenceam == NULL && + relation->rd_rel->relkind == RELKIND_SEQUENCE) + { + RelationInitSequenceAccessMethod(relation); + Assert(relation->rd_sequenceam != NULL); + + restart = true; + } /* Release hold on the relation */ RelationDecrementReferenceCount(relation); @@ -6326,8 +6376,10 @@ load_relcache_init_file(bool shared) nailed_rels++; /* Load table AM data */ - if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || rel->rd_rel->relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind)) RelationInitTableAccessMethod(rel); + else if (rel->rd_rel->relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); Assert(rel->rd_index == NULL); Assert(rel->rd_indextuple == NULL); @@ -6339,6 +6391,7 @@ load_relcache_init_file(bool shared) Assert(rel->rd_supportinfo == NULL); Assert(rel->rd_indoption == NULL); Assert(rel->rd_indcollation == NULL); + Assert(rel->rd_sequenceam == NULL); Assert(rel->rd_opcoptions == NULL); } diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 880d76aae0..8534867f43 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" @@ -4185,6 +4186,17 @@ struct config_string ConfigureNamesString[] = check_default_table_access_method, NULL, NULL }, + { + {"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default sequence access method for new sequences."), + 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/utils/misc/postgresql.conf.sample index 667e0dc40a..879c03c770 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -703,6 +703,7 @@ # error #search_path = '"$user", public' # schema names #row_security = on +#default_sequence_access_method = 'seqlocal' #default_table_access_method = 'heap' #default_tablespace = '' # a tablespace name, '' uses the default #default_toast_compression = 'pglz' # 'pglz' or 'lz4' diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 7c9a1f234c..98ff40f00d 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -161,10 +161,12 @@ describeAccessMethods(const char *pattern, bool verbose) "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")); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index a7ccde6d7d..0352c73e30 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 <name> 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/expected/create_am.out index 35d4cf1d46..f1ad1c583f 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) PARTITION 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 = 'seqlocal'; 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; @@ -519,18 +517,18 @@ FROM pg_class AS pc LEFT JOIN pg_am AS pa ON (pa.oid = pc.relam) WHERE pc.relname LIKE 'tableam_%_heapx' ORDER BY 3, 1, 2; - relkind | amname | relname ----------+--------+----------------------------- - f | | tableam_fdw_heapx - r | heap2 | tableam_parted_1_heapx - r | heap | tableam_parted_2_heapx - p | | tableam_parted_heapx - S | | tableam_seq_heapx - r | heap2 | tableam_tbl_heapx - r | heap2 | tableam_tblas_heapx - m | heap2 | tableam_tblmv_heapx - r | heap2 | tableam_tblselectinto_heapx - v | | tableam_view_heapx + relkind | amname | relname +---------+----------+----------------------------- + f | | tableam_fdw_heapx + r | heap2 | tableam_parted_1_heapx + r | heap | tableam_parted_2_heapx + p | | tableam_parted_heapx + S | seqlocal | tableam_seq_heapx + r | heap2 | tableam_tbl_heapx + r | heap2 | tableam_tblas_heapx + m | heap2 | tableam_tblmv_heapx + r | heap2 | tableam_tblselectinto_heapx + v | | tableam_view_heapx (10 rows) -- don't want to keep those tables, nor the default @@ -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_dump support +-- Checks for sequence access methods +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_local_sequenceam_handler; +-- Create and use sequence +CREATE SEQUENCE test_seqam USING local2; +SELECT nextval('test_seqam'::regclass); + nextval +--------- + 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/expected/opr_sanity.out index 0d734169f1..ea1f1048ea 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1933,6 +1933,18 @@ WHERE p1.oid = a1.amhandler AND a1.amtype = 't' AND -----+--------+-----+--------- (0 rows) +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + oid | amname | oid | proname +-----+--------+-----+--------- +(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 6aeb7cb963..beb01d78a4 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -5017,31 +5017,33 @@ Indexes: -- check printing info about access methods \dA List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA * List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA h* List of access methods @@ -5066,32 +5068,34 @@ List of access methods \dA: extra argument "bar" ignored \dA+ - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ * - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ h* List of access methods diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql index 825aed325e..472289994e 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 FROM tableam_tbl_heap2; -- CREATE VIEW doesn't support USING CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2; --- 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 tableam_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; --- 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 = 'seqlocal'; 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; -- we intentionally leave the objects created above alive, to verify pg_dump support + +-- Checks for sequence access methods + +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_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 = a1.amhandler AND a1.amtype = 't' AND OR p1.pronargs != 1 OR p1.proargtypes[0] != 'internal'::regtype); +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + -- **************** pg_amop **************** -- Look for illegal values in pg_amop fields diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 9e951a9e6f..6e1911a76d 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2579,6 +2579,7 @@ SeqScanState SeqTable SeqTableData SeqType +SequenceAmRoutine SequenceItem SerCommitSeqNo SerialControl @@ -3596,6 +3597,7 @@ lineno_t list_sort_comparator local_relopt local_relopts +local_sequence_magic local_source local_ts_iter local_ts_radix_tree @@ -3878,7 +3880,6 @@ scram_state scram_state_enum security_class_t sem_t -sepgsql_context_info_t sequence_magic set_join_pathlist_hook_type set_rel_pathlist_hook_type @@ -4100,6 +4101,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 @@ -4111,7 +4113,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 -- 2.45.2 [text/x-diff] v8-0005-Sequence-access-methods-dump-restore-support.patch (21.7K, ../../[email protected]/6-v8-0005-Sequence-access-methods-dump-restore-support.patch) download | inline diff: From fdaf5dab2b0d7f9ed0b0f2d0507f16cc7387a61f Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Mon, 26 Aug 2024 13:25:19 +0900 Subject: [PATCH v8 5/7] 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 | 47 +++++++++++++++----- 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 | 49 +++++++++++++++++++++ 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, 207 insertions(+), 11 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_backup_archiver.c index 8c20c263c4..0eb5f9de3e 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -174,6 +174,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) dopt->outputSuperuser = ropt->superuser; dopt->outputCreateDB = ropt->createDB; dopt->outputNoOwner = ropt->noOwner; + dopt->outputNoSequenceAm = ropt->noSequenceAm; dopt->outputNoTableAm = ropt->noTableAm; dopt->outputNoTablespaces = ropt->noTablespace; dopt->disable_triggers = ropt->disable_triggers; @@ -1224,6 +1225,7 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId, newToc->tag = pg_strdup(opts->tag); newToc->namespace = opts->namespace ? pg_strdup(opts->namespace) : NULL; newToc->tablespace = opts->tablespace ? pg_strdup(opts->tablespace) : NULL; + newToc->sequenceam = opts->sequenceam ? pg_strdup(opts->sequenceam) : NULL; newToc->tableam = opts->tableam ? pg_strdup(opts->tableam) : NULL; newToc->relkind = opts->relkind; newToc->owner = opts->owner ? pg_strdup(opts->owner) : NULL; @@ -2379,6 +2381,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, AH->currUser = NULL; /* unknown */ AH->currSchema = NULL; /* ditto */ + AH->currSequenceAm = NULL; /* ditto */ AH->currTablespace = NULL; /* ditto */ AH->currTableAm = NULL; /* ditto */ @@ -2608,6 +2611,7 @@ WriteToc(ArchiveHandle *AH) WriteStr(AH, te->copyStmt); WriteStr(AH, te->namespace); WriteStr(AH, te->tablespace); + WriteStr(AH, te->sequenceam); WriteStr(AH, te->tableam); WriteInt(AH, te->relkind); WriteStr(AH, te->owner); @@ -2712,6 +2716,9 @@ ReadToc(ArchiveHandle *AH) if (AH->version >= K_VERS_1_10) te->tablespace = ReadStr(AH); + if (AH->version >= K_VERS_1_17) + te->sequenceam = ReadStr(AH); + if (AH->version >= K_VERS_1_14) te->tableam = ReadStr(AH); @@ -3367,6 +3374,9 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname) free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; + free(AH->currTableAm); AH->currTableAm = NULL; @@ -3529,6 +3539,57 @@ _selectTablespace(ArchiveHandle *AH, const char *tablespace) destroyPQExpBuffer(qry); } +/* + * Set the proper default_sequence_access_method value for the sequence. + */ +static void +_selectSequenceAccessMethod(ArchiveHandle *AH, const char *sequenceam) +{ + RestoreOptions *ropt = AH->public.ropt; + PQExpBuffer cmd; + const char *want, + *have; + + /* do nothing in --no-sequence-access-method mode */ + if (ropt->noSequenceAm) + return; + + have = AH->currSequenceAm; + want = sequenceam; + + if (!want) + return; + + if (have && strcmp(want, have) == 0) + return; + + cmd = createPQExpBuffer(); + appendPQExpBuffer(cmd, + "SET default_sequence_access_method = %s;", + fmtId(want)); + + if (RestoringToDB(AH)) + { + PGresult *res; + + res = PQexec(AH->connection, cmd->data); + + if (!res || PQresultStatus(res) != 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 = pg_strdup(want); +} + /* * Set the proper default_table_access_method value for the table. */ @@ -3738,6 +3799,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData) _becomeOwner(AH, te); _selectOutputSchema(AH, te->namespace); _selectTablespace(AH, te->tablespace); + _selectSequenceAccessMethod(AH, te->sequenceam); if (te->relkind != RELKIND_PARTITIONED_TABLE) _selectTableAccessMethod(AH, te->tableam); @@ -4262,6 +4324,8 @@ restore_toc_entries_prefork(ArchiveHandle *AH, TocEntry *pending_list) AH->currUser = NULL; free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; free(AH->currTablespace); AH->currTablespace = NULL; free(AH->currTableAm); @@ -5001,6 +5065,7 @@ CloneArchive(ArchiveHandle *AH) clone->connCancel = NULL; clone->currUser = NULL; clone->currSchema = NULL; + clone->currSequenceAm = NULL; clone->currTableAm = NULL; clone->currTablespace = NULL; @@ -5060,6 +5125,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_backup_archiver.h index ce5ed1dd39..cdfebcd340 100644 --- a/src/bin/pg_dump/pg_backup_archiver.h +++ b/src/bin/pg_dump/pg_backup_archiver.h @@ -71,10 +71,11 @@ #define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0) /* BLOB METADATA entries * and multiple BLOBS, * relkind */ +#define K_VERS_1_17 MAKE_ARCHIVE_VERSION(1, 17, 0) /* add sequenceam */ /* 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_VERS_REV) @@ -322,6 +323,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 */ @@ -353,6 +355,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 relkind; /* relation kind, only for TABLE tags */ char *owner; @@ -394,6 +397,7 @@ typedef struct _archiveOpts const char *tag; const char *namespace; const char *tablespace; + const char *sequenceam; const char *tableam; char relkind; const char *owner; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index f7720ad53b..76d71c4f4b 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -134,6 +134,7 @@ typedef struct int64 cache; /* cache size */ int64 last_value; /* last value of sequence */ bool is_called; /* whether nextval advances before returning */ + char *seqam; /* access method of sequence */ } SequenceItem; typedef enum OidOptions @@ -477,6 +478,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}, @@ -1098,6 +1100,7 @@ main(int argc, char **argv) ropt->superuser = dopt.outputSuperuser; ropt->createDB = dopt.outputCreateDB; ropt->noOwner = dopt.outputNoOwner; + ropt->noSequenceAm = dopt.outputNoSequenceAm; ropt->noTableAm = dopt.outputNoTableAm; ropt->noTablespace = dopt.outputNoTablespaces; ropt->disable_triggers = dopt.disable_triggers; @@ -1214,6 +1217,7 @@ help(const char *progname) printf(_(" --no-publications do not dump publications\n")); printf(_(" --no-security-labels do not dump security label assignments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); + printf(_(" --no-sequence-access-method do not sequence table access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); @@ -13103,6 +13107,9 @@ dumpAccessMethod(Archive *fout, const AccessMethodInfo *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; @@ -17382,26 +17389,40 @@ collectSequences(Archive *fout) * * Since version 18, we can gather the sequence data in this query with * pg_get_sequence_data(), but we only do so for non-schema-only dumps. + * + * Access methods for sequences are supported since version 18. */ if (fout->remoteVersion < 100000) return; - else if (fout->remoteVersion < 180000 || - (fout->dopt->schemaOnly && !fout->dopt->sequence_data)) + else if (fout->remoteVersion < 180000) query = "SELECT seqrelid, format_type(seqtypid, NULL), " "seqstart, seqincrement, " "seqmax, seqmin, " "seqcache, seqcycle, " - "NULL, 'f' " + "NULL, 'f', NULL " "FROM pg_catalog.pg_sequence " "ORDER BY seqrelid"; + else if (fout->dopt->schemaOnly && !fout->dopt->sequence_data) + query = "SELECT s.seqrelid, format_type(s.seqtypid, NULL), " + "s.seqstart, s.seqincrement, " + "s.seqmax, s.seqmin, " + "s.seqcache, s.seqcycle, " + "NULL, 'f', a.amname AS seqam " + "FROM pg_catalog.pg_sequence s " + "JOIN pg_class c ON (c.oid = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam) " + "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_get_sequence_data(seqrelid) " + query = "SELECT s.seqrelid, format_type(s.seqtypid, NULL), " + "s.seqstart, s.seqincrement, " + "s.seqmax, s.seqmin, " + "s.seqcache, s.seqcycle, " + "r.last_value, r.is_called, " + "a.amname AS seqam " + "FROM pg_catalog.pg_sequence s " + "JOIN pg_class c ON (c.oid = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam), " + "pg_get_sequence_data(s.seqrelid) r " "ORDER BY seqrelid;"; res = ExecuteSqlQuery(fout, query, PGRES_TUPLES_OK); @@ -17421,6 +17442,10 @@ collectSequences(Archive *fout) 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); + if (!PQgetisnull(res, i, 10)) + sequences[i].seqam = pg_strdup(PQgetvalue(res, i, 10)); + else + sequences[i].seqam = NULL; } PQclear(res); @@ -17492,6 +17517,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) 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); + seq->seqam = NULL; PQclear(res); } @@ -17613,6 +17639,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) ARCHIVE_OPTS(.tag = tbinfo->dobj.name, .namespace = tbinfo->dobj.namespace->dobj.name, .owner = tbinfo->rolname, + .sequenceam = seq->seqam, .description = "SEQUENCE", .section = SECTION_PRE_DATA, .createStmt = query->data, diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index e3ad8fb295..865f11702f 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 = 0; static int disable_triggers = 0; static int if_exists = 0; static int inserts = 0; +static int no_sequence_access_method = 0; static int no_table_access_method = 0; static int no_tablespaces = 0; static int use_setsessauth = 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 assignments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); printf(_(" --no-sync do not wait for changes to be written safely to disk\n")); + printf(_(" --no-sequence-access-method do not dump sequence access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c index df119591cc..081954c5ce 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 = 0; static int if_exists = 0; static int no_data_for_failed_tables = 0; + static int outputNoSequenceAm = 0; static int outputNoTableAm = 0; static int outputNoTablespaces = 0; static int use_setsessauth = 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 = disable_triggers; opts->enable_row_security = enable_row_security; opts->noDataForFailedTables = no_data_for_failed_tables; + opts->noSequenceAm = outputNoSequenceAm; opts->noTableAm = outputNoTableAm; opts->noTablespace = outputNoTablespaces; opts->use_setsessauth = 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 access methods\n")); printf(_(" --no-table-access-method do not restore table access methods\n")); printf(_(" --no-tablespaces do not restore tablespace assignments\n")); printf(_(" --section=SECTION restore named section (pre-data, 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_dump.pl index 5bcc2244d5..a6128f7ce2 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 = ( 'postgres', ], }, + no_sequence_access_method => { + dump_cmd => [ + 'pg_dump', '--no-sync', + "--file=$tempdir/no_sequence_access_method.sql", + '--no-sequence-access-method', 'postgres', + ], + }, no_table_access_method => { dump_cmd => [ 'pg_dump', '--no-sync', @@ -711,6 +718,7 @@ my %full_runs = ( no_large_objects => 1, no_owner => 1, no_privs => 1, + no_sequence_access_method => 1, no_table_access_method => 1, pg_dumpall_dbprivs => 1, pg_dumpall_exclude => 1, @@ -4488,6 +4496,18 @@ my %tests = ( }, }, + 'CREATE ACCESS METHOD regress_test_sequence_am' => { + create_order => 11, + create_sql => + 'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;', + regexp => qr/^ + \QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;\E + \n/xm, + like => { + %full_runs, section_pre_data => 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 @@ -4516,6 +4536,35 @@ my %tests = ( }, }, + + # 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' => { + create_order => 12, + create_sql => ' + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_0 USING seqlocal; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_1 USING regress_sequence_am; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_2 USING seqlocal;', + regexp => qr/^ + \QSET default_sequence_access_method = 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 => { + %full_runs, %dump_test_schema_runs, section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + no_sequence_access_method => 1, + only_dump_measurement => 1, + }, + }, + 'CREATE MATERIALIZED VIEW regress_pg_dump_matview_am' => { create_order => 13, create_sql => ' diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index ffc29b04fb..0c392a8080 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -1121,6 +1121,23 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + + <para> + This option is ignored when emitting an archive (non-text) output + file. For the archive formats, you can specify the option when you + call <command>pg_restore</command>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml 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 <replaceable class="parameter">PATTERN</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml index b8b27e1719..922f69eb4a 100644 --- a/doc/src/sgml/ref/pg_restore.sgml +++ b/doc/src/sgml/ref/pg_restore.sgml @@ -733,6 +733,17 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> -- 2.45.2 [text/x-diff] v8-0006-Sequence-access-methods-core-documentation.patch (9.5K, ../../[email protected]/7-v8-0006-Sequence-access-methods-core-documentation.patch) download | inline diff: From 7de0149b2b69a133ef8762cce67a16ed2defd087 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:55:56 +0900 Subject: [PATCH v8 6/7] 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 12feac6087..651caa7491 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8993,6 +8993,22 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-default-sequence-access-method" xreflabel="default_sequence_access_method"> + <term><varname>default_sequence_access_method</varname> (<type>string</type>) + <indexterm> + <primary><varname>default_sequence_access_method</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + This parameter specifies the default sequence access method to use when + creating sequences if the <command>CREATE SEQUENCE</command> + command does not explicitly specify an access method. The default is + <literal>local</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-default-tablespace" xreflabel="default_tablespace"> <term><varname>default_tablespace</varname> (<type>string</type>) <indexterm> diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index a7ff5f8264..07aa3f18c7 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -95,6 +95,7 @@ <!ENTITY planstats SYSTEM "planstats.sgml"> <!ENTITY tableam SYSTEM "tableam.sgml"> <!ENTITY indexam SYSTEM "indexam.sgml"> +<!ENTITY sequenceam SYSTEM "sequenceam.sgml"> <!ENTITY nls SYSTEM "nls.sgml"> <!ENTITY plhandler SYSTEM "plhandler.sgml"> <!ENTITY fdwhandler SYSTEM "fdwhandler.sgml"> 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 <replaceable class="parameter">name</replaceable> <listitem> <para> This clause specifies the type of access method to define. - Only <literal>TABLE</literal> and <literal>INDEX</literal> - are supported at present. + Only <literal>TABLE</literal>, <literal>SEQUENCE</literal> and + <literal>INDEX</literal> are supported at present. </para> </listitem> </varlistentry> @@ -77,12 +77,15 @@ CREATE ACCESS METHOD <replaceable class="parameter">name</replaceable> declared to take a single argument of type <type>internal</type>, and its return type depends on the type of access method; for <literal>TABLE</literal> access methods, it must - be <type>table_am_handler</type> and for <literal>INDEX</literal> - access methods, it must be <type>index_am_handler</type>. + be <type>table_am_handler</type>; for <literal>INDEX</literal> + access methods, it must be <type>index_am_handler</type>; + for <literal>SEQUENCE</literal>, it must be + <type>sequence_am_handler</type>; 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 <xref linkend="tableam"/> and the index access method - API is described in <xref linkend="indexam"/>. + is described in <xref linkend="tableam"/>, the index access method + API is described in <xref linkend="indexam"/> and the sequence access + method is described in <xref linkend="sequenceam"/>. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/create_sequence.sgml index 1e283f13d1..52c6096e4b 100644 --- a/doc/src/sgml/ref/create_sequence.sgml +++ b/doc/src/sgml/ref/create_sequence.sgml @@ -29,6 +29,7 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceab [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ] + [ USING <replaceable class="parameter">access_method</replaceable> ] </synopsis> </refsynopsisdiv> @@ -263,6 +264,17 @@ SELECT * FROM <replaceable>name</replaceable>; </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>USING</literal> <replaceable class="parameter">access_method</replaceable></term> + <listitem> + <para> + The <literal>USING</literal> option specifies which sequence access + method will be used when generating the sequence numbers. The default + is <literal>local</literal>. + </para> + </listitem> + </varlistentry> </variablelist> </refsect1> 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 @@ +<!-- doc/src/sgml/sequenceam.sgml --> + +<chapter id="sequenceam"> + <title>Sequence Access Method Interface Definition</title> + + <indexterm> + <primary>Sequence Access Method</primary> + </indexterm> + <indexterm> + <primary>sequenceam</primary> + <secondary>Sequence Access Method</secondary> + </indexterm> + + <para> + This chapter explains the interface between the core + <productname>PostgreSQL</productname> system and <firstterm>sequence access + methods</firstterm>, 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. + </para> + + <para> + Each sequence access method is described by a row in the + <link linkend="catalog-pg-am"><structname>pg_am</structname></link> system + catalog. The <structname>pg_am</structname> entry specifies a name and a + <firstterm>handler function</firstterm> for the sequence access method. These + entries can be created and deleted using the + <xref linkend="sql-create-access-method"/> and + <xref linkend="sql-drop-access-method"/> SQL commands. + </para> + + <para> + A sequence access method handler function must be declared to accept a single + argument of type <type>internal</type> and to return the pseudo-type + <type>sequence_am_handler</type>. The argument is a dummy value that simply + 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 + <structname>SequenceAmRoutine</structname>, which contains everything that the + core code needs to know to make use of the sequence access method. The return + value needs to be of server lifetime, which is typically achieved by + defining it as a <literal>static const</literal> variable in global + scope. The <structname>SequenceAmRoutine</structname> struct, also called the + access method's <firstterm>API struct</firstterm>, 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 + <structname>SequenceAmRoutine</structname> structure (with comments inside + 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 <ulink url="https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/include/access/sequenceam.h;hb=HEAD"> + <filename>src/include/access/sequenceam.h</filename></ulink> file. + </para> + + <para> + Currently, the way a sequence access method stores data is fairly + unconstrained, and it is possible to use a predefined + <link linkend="tableam">Table Access Method</link> to store sequence + data. + </para> + + <para> + For crash safety, a sequence access method can use + <link linkend="wal"><acronym>WAL</acronym></link>, or a custom + implementation. + If <acronym>WAL</acronym> is chosen, either + <link linkend="generic-wal">Generic WAL Records</link> can be used, or a + <link linkend="custom-rmgr">Custom WAL Resource Manager</link> can be + implemented. + </para> + + <para> + Any developer of a new <literal>sequence access method</literal> can refer to + the existing <literal>local</literal> implementation present in + <filename>src/backend/access/sequence/local.c</filename> for details of + its implementation. + </para> +</chapter> -- 2.45.2 [text/x-diff] v8-0007-snowflake-Add-sequence-AM-based-on-it.patch (28.0K, ../../[email protected]/8-v8-0007-snowflake-Add-sequence-AM-based-on-it.patch) download | inline diff: From d799414f07c10bb82a252ef2c1148361f5e99ae1 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 26 Apr 2024 15:09:00 +0900 Subject: [PATCH v8 7/7] snowflake: Add sequence AM based on it This includes documentation and a basic implementation, though this is in a very early stage. --- doc/src/sgml/contrib.sgml | 1 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/snowflake.sgml | 100 ++++ contrib/Makefile | 1 + contrib/meson.build | 1 + contrib/snowflake/.gitignore | 3 + contrib/snowflake/Makefile | 19 + contrib/snowflake/expected/snowflake.out | 73 +++ contrib/snowflake/meson.build | 34 ++ contrib/snowflake/snowflake--1.0.sql | 21 + contrib/snowflake/snowflake.c | 570 +++++++++++++++++++++++ contrib/snowflake/snowflake.control | 5 + contrib/snowflake/sql/snowflake.sql | 29 ++ 13 files changed, 858 insertions(+) create mode 100644 doc/src/sgml/snowflake.sgml create mode 100644 contrib/snowflake/.gitignore create mode 100644 contrib/snowflake/Makefile create mode 100644 contrib/snowflake/expected/snowflake.out create mode 100644 contrib/snowflake/meson.build create mode 100644 contrib/snowflake/snowflake--1.0.sql create mode 100644 contrib/snowflake/snowflake.c create mode 100644 contrib/snowflake/snowflake.control create mode 100644 contrib/snowflake/sql/snowflake.sql diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml index 44639a8dca..e7b8871e8a 100644 --- a/doc/src/sgml/contrib.sgml +++ b/doc/src/sgml/contrib.sgml @@ -166,6 +166,7 @@ CREATE EXTENSION <replaceable>extension_name</replaceable>; &seg; &sepgsql; &contrib-spi; + &snowflake; &sslinfo; &tablefunc; &tcn; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 07aa3f18c7..496ac19f25 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -156,6 +156,7 @@ <!ENTITY seg SYSTEM "seg.sgml"> <!ENTITY contrib-spi SYSTEM "contrib-spi.sgml"> <!ENTITY sepgsql SYSTEM "sepgsql.sgml"> +<!ENTITY snowflake SYSTEM "snowflake.sgml"> <!ENTITY sslinfo SYSTEM "sslinfo.sgml"> <!ENTITY tablefunc SYSTEM "tablefunc.sgml"> <!ENTITY tcn SYSTEM "tcn.sgml"> diff --git a/doc/src/sgml/snowflake.sgml b/doc/src/sgml/snowflake.sgml new file mode 100644 index 0000000000..060699e7ec --- /dev/null +++ b/doc/src/sgml/snowflake.sgml @@ -0,0 +1,100 @@ +<!-- doc/src/sgml/snowflake.sgml --> + +<sect1 id="snowflake" xreflabel="snowflake"> + <title>snowflake — sequence access method</title> + + <indexterm zone="snowflake"> + <primary>snowflake</primary> + </indexterm> + + <para> + <literal>snowflake</literal> provides a sequence access method based on + <ulink url="https://en.wikipedia.org/wiki/Snowflake_ID">Snowflake IDs</ulink>. + </para> + + <para> + A Snowflake ID (or snowflake) is a unique 64-bit identifier made of three + components: + <itemizedlist spacing="compact"> + <listitem><para>41 bits for a timestamp, epoch-adjusted in milli-seconds</para></listitem> + <listitem><para>10 bits for machine ID</para></listitem> + <listitem><para>12 bits for a sequence number</para></listitem> + </itemizedlist> + </para> + + <sect2 id="snowflake-functions"> + <title>Functions</title> + + <variablelist> + <varlistentry> + <term> + <function>snowflake_get(raw int8) returns record</function> + <indexterm> + <primary>snowflake_get</primary> + <secondary>function</secondary> + </indexterm> + </term> + + <listitem> + <para> + Returns a record made of the timestamp in milli-seconds, the machine ID + and the sequence number for a single snowflake ID. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-parameters"> + <title>Configuration Parameters</title> + + <variablelist> + <varlistentry> + <term> + <varname>snowflake.machine_id</varname> + <indexterm> + <primary><varname>snowflake.machine_id</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Machine ID assigned to the snowflake IDs used in the sequence. The + default value is <literal>1</literal>. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-examples"> + <title>Examples</title> + + <para> + This is an example of creating a snowflake sequence: + </para> + +<programlisting> +CREATE SEQUENCE snowflake_seq USING snowflake; +</programlisting> + + <para> + Similarly to the default sequence access method, snowflake sequences + can be queried as a table: + </para> + +<programlisting> + =# SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) +=# SELECT to_timestamp(time_ms / 1000), machine, counter + FROM snowflake_get(nextval('snowflake_seq')); + to_timestamp | machine | counter +------------------------+---------+--------- + 2024-04-26 14:28:26+09 | 1 | 3 +(1 row) +</programlisting> + </sect2> + +</sect1> diff --git a/contrib/Makefile b/contrib/Makefile index abd780f277..f5d1b568b2 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -42,6 +42,7 @@ SUBDIRS = \ pg_walinspect \ postgres_fdw \ seg \ + snowflake \ spi \ tablefunc \ tcn \ diff --git a/contrib/meson.build b/contrib/meson.build index 14a8906865..4f2603e7e9 100644 --- a/contrib/meson.build +++ b/contrib/meson.build @@ -57,6 +57,7 @@ subdir('pg_walinspect') subdir('postgres_fdw') subdir('seg') subdir('sepgsql') +subdir('snowflake') subdir('spi') subdir('sslinfo') # start-scripts doesn't contain build products diff --git a/contrib/snowflake/.gitignore b/contrib/snowflake/.gitignore new file mode 100644 index 0000000000..44d119cfcc --- /dev/null +++ b/contrib/snowflake/.gitignore @@ -0,0 +1,3 @@ +# Generated subdirectories +/log/ +/results/ diff --git a/contrib/snowflake/Makefile b/contrib/snowflake/Makefile new file mode 100644 index 0000000000..fa5b48d565 --- /dev/null +++ b/contrib/snowflake/Makefile @@ -0,0 +1,19 @@ +# contrib/snowflake/Makefile + +MODULES = snowflake + +EXTENSION = snowflake +DATA = snowflake--1.0.sql + +REGRESS = snowflake + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/snowflake +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/snowflake/expected/snowflake.out b/contrib/snowflake/expected/snowflake.out new file mode 100644 index 0000000000..b7e469bf73 --- /dev/null +++ b/contrib/snowflake/expected/snowflake.out @@ -0,0 +1,73 @@ +CREATE EXTENSION snowflake; +CREATE SEQUENCE snowflake_seq USING snowflake; +SET snowflake.machine_id = 2000; -- error +ERROR: 2000 is outside the valid range for parameter "snowflake.machine_id" (0 .. 1023) +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(lastval()); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 3 | t +(1 row) + +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + relpersistence +---------------- + u +(1 row) + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; + machine | counter +---------+--------- + 4 | 2 + 4 | 3 + 4 | 4 + 4 | 5 + 4 | 6 + 4 | 7 + 4 | 8 + 4 | 9 + 4 | 10 + 4 | 11 +(10 rows) + +DROP TABLE snowflake_tab; +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; diff --git a/contrib/snowflake/meson.build b/contrib/snowflake/meson.build new file mode 100644 index 0000000000..567669eea7 --- /dev/null +++ b/contrib/snowflake/meson.build @@ -0,0 +1,34 @@ +# Copyright (c) 2024, PostgreSQL Global Development Group + +snowflake_sources = files( + 'snowflake.c', +) + +if host_system == 'windows' + snowflake_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'snowflake', + '--FILEDESC', 'snowflake - sequence access method',]) +endif + +snowflake = shared_module('snowflake', + snowflake_sources, + kwargs: contrib_mod_args, +) +contrib_targets += bloom + +install_data( + 'snowflake.control', + 'snowflake--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'snowflake', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'snowflake', + ], + }, +} diff --git a/contrib/snowflake/snowflake--1.0.sql b/contrib/snowflake/snowflake--1.0.sql new file mode 100644 index 0000000000..bcb9d754f1 --- /dev/null +++ b/contrib/snowflake/snowflake--1.0.sql @@ -0,0 +1,21 @@ +/* contrib/snowflake/snowflake--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION snowflake" to load this file. \quit + +CREATE FUNCTION snowflake_sequenceam_handler(internal) + RETURNS sequence_am_handler + AS 'MODULE_PATHNAME' + LANGUAGE C; + +CREATE ACCESS METHOD snowflake + TYPE SEQUENCE HANDLER snowflake_sequenceam_handler; +COMMENT ON ACCESS METHOD snowflake IS 'snowflake sequence access method'; + +CREATE FUNCTION snowflake_get(IN raw int8, + OUT time_ms int8, + OUT machine int4, + OUT counter int4) + RETURNS record + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT diff --git a/contrib/snowflake/snowflake.c b/contrib/snowflake/snowflake.c new file mode 100644 index 0000000000..be4c4039ec --- /dev/null +++ b/contrib/snowflake/snowflake.c @@ -0,0 +1,570 @@ +/*------------------------------------------------------------------------- + * + * snowflake.c + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * contrib/snowflake/snowflake.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <sys/time.h> + +#include "access/generic_xlog.h" +#include "access/sequenceam.h" +#include "access/xact.h" +#include "catalog/storage_xlog.h" +#include "commands/tablecmds.h" +#include "fmgr.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "storage/bufmgr.h" +#include "utils/guc.h" +#include "utils/numeric.h" + +PG_MODULE_MAGIC; + +/* "special area" of a snowflake's buffer page. */ +#define SNOWFLAKE_MAGIC 0x01 + +typedef struct snowflake_magic +{ + uint32 magic; +} snowflake_magic; + +/* ----------------------------------------------------------------------- + * Snowflake ID are 64-bit based, with the following structure: + * - 41 bits for an epoch-based timestamp, in milli-seconds. + * - 10 bits for a machine ID. + * - 12 bits for a sequence counter. + * + * The timestamp can be cut to an offset. The machine ID is controlled + * by a superuser GUC. Sequence properties apply to the sequence counter, + * as the other two are environment-dependent. + * ----------------------------------------------------------------------- + */ + +/* + * Helper routines to convert a snowflake ID from/to an int64. + */ +#define SNOWFLAKE_COUNTER_MASK 0x0000000000000FFF /* 12 bits */ +#define SNOWFLAKE_COUNTER_SHIFT 0 +#define SNOWFLAKE_MACHINE_ID_MASK 0x00000000000003FF /* 10 bits */ +#define SNOWFLAKE_MACHINE_ID_SHIFT 12 /* counter */ +#define SNOWFLAKE_TIMESTAMP_MASK 0x000001FFFFFFFFFF /* 41 bits */ +#define SNOWFLAKE_TIMESTAMP_SHIFT 22 /* machine ID + counter sizes */ + +typedef struct snowflake_id +{ + uint32 count; + uint32 machine; + uint64 time_ms; +} snowflake_id; + +#define snowflake_id_to_int64(id, raw) { \ + raw = (((id).count) & SNOWFLAKE_COUNTER_MASK) << SNOWFLAKE_COUNTER_SHIFT | \ + (((id).machine) & SNOWFLAKE_MACHINE_ID_MASK) << SNOWFLAKE_MACHINE_ID_SHIFT | \ + (((id).time_ms) & SNOWFLAKE_TIMESTAMP_MASK) << SNOWFLAKE_TIMESTAMP_SHIFT; \ +} + +#define int64_to_snowflake_id(raw, id) { \ + (id).count = ((raw) >> SNOWFLAKE_COUNTER_SHIFT) & SNOWFLAKE_COUNTER_MASK; \ + (id).machine = ((raw) >> SNOWFLAKE_MACHINE_ID_SHIFT) & SNOWFLAKE_MACHINE_ID_MASK; \ + (id).time_ms = ((raw) >> SNOWFLAKE_TIMESTAMP_SHIFT) & SNOWFLAKE_TIMESTAMP_MASK; \ +} + +/* + * Format of tuples stored in heap table associated to snowflake sequence. + */ +typedef struct FormData_snowflake_data +{ + /* enough to cover 12 bits of the internal counter */ + int16 count; + bool is_called; +} FormData_snowflake_data; + +typedef FormData_snowflake_data *Form_snowflake_data; + +/* + * Columns of a snowflake sequence relation. + */ +#define SNOWFLAKE_COL_COUNT 1 +#define SNOWFLAKE_COL_CALLED 2 + +#define SNOWFLAKE_COLS 2 + +/* GUC parameter */ +static int snowflake_machine_id = 1; + +PG_FUNCTION_INFO_V1(snowflake_sequenceam_handler); + +/* ----------------------------------------------------------------------- + * Interfaces for relation manipulation. + * ----------------------------------------------------------------------- + */ + +/* + * Initialize snowflake relation's fork with some data. + */ +static void +fill_snowflake_fork(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + snowflake_magic *sm; + OffsetNumber offnum; + GenericXLogState *state = NULL; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(snowflake_magic)); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + sm->magic = SNOWFLAKE_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= HEAP_XMAX_INVALID; + ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber); + + /* + * Initialize before entering in the critical section, as this does + * allocations. + */ + if (forkNum == INIT_FORKNUM) + state = GenericXLogStart(rel); + + START_CRIT_SECTION(); + + MarkBufferDirty(buf); + + offnum = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* + * Init forks have to be logged. These go through generic WAL records + * for simplicity's sake to save from the need of a custom RMGR. + */ + if (forkNum == INIT_FORKNUM) + { + (void) GenericXLogRegisterBuffer(state, buf, GENERIC_XLOG_FULL_IMAGE); + GenericXLogFinish(state); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * Initialize snowflake relation. + * + * This needs to handle both the initial and main forks. + */ +static void +fill_snowflake(Relation rel, HeapTuple tuple) +{ + SMgrRelation srel; + + Assert(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED); + + fill_snowflake_fork(rel, tuple, MAIN_FORKNUM); + + /* init fork */ + srel = smgropen(rel->rd_locator, INVALID_PROC_NUMBER); + smgrcreate(srel, INIT_FORKNUM, false); + log_smgrcreate(&rel->rd_locator, INIT_FORKNUM); + fill_snowflake_fork(rel, tuple, INIT_FORKNUM); + FlushRelationBuffers(rel); + smgrclose(srel); +} + +/* + * Read the current state of a snowflake sequence + * + * Given an opened sequence relation, lock the page buffer and find the tuple. + * + * *buf receives the reference to the pinned-and-ex-locked buffer. + * *seqdatatuple receives the reference to the sequence tuple proper. + * + * Returns value points to the data payload of the tuple. + */ +static Form_snowflake_data +read_snowflake(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + snowflake_magic *sm; + Form_snowflake_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SNOWFLAKE_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_snowflake_data) GETSTRUCT(seqdatatuple); + + return seq; +} + + +/* ------------------------------------------------------------------------ + * Callbacks for the snowflake 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 * +snowflake_sequenceam_get_table_am(void) +{ + return "heap"; +} + +/* + * snowflake_sequenceam_init + * + * Initialize relation of a snowflake sequence. This stores the sequence + * counter in an unlogged relation as timestamps ensure value unicity. + */ +static void +snowflake_sequenceam_init(Relation rel, int64 last_value, bool is_called) +{ + Datum values[SNOWFLAKE_COLS]; + bool nulls[SNOWFLAKE_COLS]; + int16 counter; + TupleDesc tupdesc; + HeapTuple tuple; + List *elts = NIL; + ListCell *lc; + ColumnDef *coldef = NULL; + AlterTableCmd *atcmd; + List *atcmds = NIL; + + /* Adjust last_value, depending on the defaults given */ + counter = ((int16) last_value) & SNOWFLAKE_COUNTER_MASK; + + /* + * Create unlogged relation with its attributes. + */ + coldef = makeColumnDef("count", INT2OID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + + foreach(lc, elts) + { + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + /* + * Switch the relation to be unlogged. This forces a rewrite, but + * the relation is empty so that's OK. + */ + RelationSetNewRelfilenumber(rel, RELPERSISTENCE_UNLOGGED); + + /* And insert its first tuple */ + values[0] = Int16GetDatum(counter); + nulls[0] = false; + values[1] = BoolGetDatum(is_called); + nulls[1] = false; + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, values, nulls); + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_nextval + * + * Return the next value for a snowflake sequence. + */ +static int64 +snowflake_sequenceam_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + Buffer buf; + Form_snowflake_data seq; + HeapTupleData seqdatatuple; + int64 result = 0; + snowflake_id id = {0}; + struct timeval tp; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* + * The logic here is quite simple, increment the counter until its + * threshold is reached and get back to the start. If the threshold + * is reached, wait 1ms to ensure a unique timestamp. There is no + * need to do a retry as the buffer is already locked. + */ + id.count = seq->count; + id.count++; + + if (id.count > (PG_INT16_MAX & SNOWFLAKE_COUNTER_MASK)) + { + /* + * Threshold reached, so wait a bit for force clock to a new + * timestamp. + */ + id.count = 1; + pg_usleep(1000L); /* 1ms */ + } + + /* Compute timestamp, with buffer locked */ + gettimeofday(&tp, NULL); + id.time_ms = (uint64) tp.tv_sec * 1000 + + tp.tv_usec / 1000; + + /* Machine ID */ + id.machine = snowflake_machine_id; + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->count = id.count; + seq->is_called = true; + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + /* Store the last value computed for lastval() */ + snowflake_id_to_int64(id, result); + *last = result; + return result; +} + +/* + * snowflake_sequenceam_setval + * + * Set the sequence value, manipulating only the sequence counter. + */ +static void +snowflake_sequenceam_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* Change the in-buffer tuple */ + START_CRIT_SECTION(); + seq->count = (next & SNOWFLAKE_COUNTER_MASK); + seq->is_called = iscalled; + MarkBufferDirty(buf); + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_get_state + * + * Return the last sequence counter value. + */ +static void +snowflake_sequenceam_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + seq = read_snowflake(rel, &buf, &seqdatatuple); + *last_value = seq->count; + *is_called = seq->is_called; + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_reset + * + * Reset the sequence, coming down to resetting its counter. + */ +static void +snowflake_sequenceam_reset(Relation rel, int64 startv, bool is_called, + bool reset_state) +{ + HeapTupleData seqdatatuple; + HeapTuple tuple; + Form_snowflake_data seq; + Buffer buf; + + /* lock buffer page and read tuple */ + (void) read_snowflake(rel, &buf, &seqdatatuple); + + /* copy the existing tuple */ + tuple = 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 = (Form_snowflake_data) GETSTRUCT(tuple); + seq->count = (startv & SNOWFLAKE_COUNTER_MASK); + seq->is_called = is_called; + + /* create new storage */ + RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence); + + /* insert the modified tuple into the page */ + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_change_persistence + * + * There is nothing to do here, the underneath relation has to remain + * unlogged and is set as such when creating the sequence. + */ +static void +snowflake_sequenceam_change_persistence(Relation rel, char newrelpersistence) +{ + /* Nothing to do here */ +} + +/* ------------------------------------------------------------------------ + * Definition of the snowflake sequence access method. + * ------------------------------------------------------------------------ + */ + +static const SequenceAmRoutine snowflake_sequenceam_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = snowflake_sequenceam_get_table_am, + .init = snowflake_sequenceam_init, + .nextval = snowflake_sequenceam_nextval, + .setval = snowflake_sequenceam_setval, + .get_state = snowflake_sequenceam_get_state, + .reset = snowflake_sequenceam_reset, + .change_persistence = snowflake_sequenceam_change_persistence +}; + +Datum +snowflake_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&snowflake_sequenceam_methods); +} + +/* Utility functions */ + +/* + * snowflake_get + * + * Return a tuple worth of snowflake ID data, in a readable shape. + */ +PG_FUNCTION_INFO_V1(snowflake_get); +Datum +snowflake_get(PG_FUNCTION_ARGS) +{ +#define SNOWFLAKE_GET_COLS 3 + int64 raw = PG_GETARG_INT64(0); + Datum *values; + bool *nulls; + TupleDesc tupdesc; + snowflake_id id; + + /* determine result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + int64_to_snowflake_id(raw, id); + + nulls = palloc0(sizeof(bool) * tupdesc->natts); + values = palloc0(sizeof(Datum) * tupdesc->natts); + + values[0] = Int64GetDatum(id.time_ms); + values[1] = Int32GetDatum(id.machine); + values[2] = Int32GetDatum(id.count); + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Entry point when loading extension. + */ +void +_PG_init(void) +{ + DefineCustomIntVariable("snowflake.machine_id", + "Machine ID to use with snowflake sequence.", + "Default value is 1.", + &snowflake_machine_id, + 1, 0, 1023, /* 10 bits as max */ + PGC_SUSET, + 0, NULL, NULL, NULL); +} diff --git a/contrib/snowflake/snowflake.control b/contrib/snowflake/snowflake.control new file mode 100644 index 0000000000..7b8c6089c2 --- /dev/null +++ b/contrib/snowflake/snowflake.control @@ -0,0 +1,5 @@ +# snowflake extension +comment = 'snowflake - sequence access method' +default_version = '1.0' +module_pathname = '$libdir/snowflake' +relocatable = true diff --git a/contrib/snowflake/sql/snowflake.sql b/contrib/snowflake/sql/snowflake.sql new file mode 100644 index 0000000000..395d166ba4 --- /dev/null +++ b/contrib/snowflake/sql/snowflake.sql @@ -0,0 +1,29 @@ +CREATE EXTENSION snowflake; + +CREATE SEQUENCE snowflake_seq USING snowflake; + +SET snowflake.machine_id = 2000; -- error +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(lastval()); +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; +DROP TABLE snowflake_tab; + +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; -- 2.45.2 [application/pgp-signature] signature.asc (833B, ../../[email protected]/9-signature.asc) download ^ permalink raw reply [nested|flat] 14+ messages in thread
* Re: Sequence Access Methods, round two @ 2024-10-02 05:28 Michael Paquier <[email protected]> parent: Michael Paquier <[email protected]> 0 siblings, 1 reply; 14+ messages in thread From: Michael Paquier @ 2024-10-02 05:28 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; +Cc: Peter Smith <[email protected]>; Postgres hackers <[email protected]> On Fri, Aug 30, 2024 at 05:24:19PM +0900, Michael Paquier wrote: > Following a83a944e9fdd, rebased as v8. Rebased as v29 due to an OID conflict. -- Michael Attachments: [text/x-diff] v9-0001-Remove-FormData_pg_sequence_data-from-init_params.patch (9.2K, ../../[email protected]/2-v9-0001-Remove-FormData_pg_sequence_data-from-init_params.patch) download | inline diff: From 4f5853a61e20d49f641f312bfb62d183a7948f3b Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:00:45 +0900 Subject: [PATCH v9 1/7] 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/sequence.c index 0188e8bbd5..ed2cd2b1fc 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 rel, 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); 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 = makeNode(CreateStmt); @@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) /* 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); /* @@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) { case SEQ_COL_LASTVAL: coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(seqdataform.last_value); + value[i - 1] = Int64GetDatumFast(last_value); break; case SEQ_COL_LOG: coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); @@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) ObjectAddress address; Relation rel; HeapTuple seqtuple; + bool reset_state = false; + bool is_called; + int64 last_value; HeapTuple newdatatuple; /* 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 = heap_copytuple(&datatuple); newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); + last_value = newdataform->last_value; + is_called = newdataform->is_called; UnlockReleaseBuffer(buf); /* 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); /* 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 = last_value; + newdataform->is_called = is_called; + if (reset_state) + newdataform->log_cnt = 0; fill_seq_with_data(seqrel, newdatatuple); } @@ -1236,17 +1249,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). Also + * 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 overridden. * * Note: we force a sequence rewrite whenever we change parameters that affect - * 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. Currently, * 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 @@ -1257,7 +1272,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) { @@ -1363,11 +1380,11 @@ init_params(ParseState *pstate, List *options, bool for_identity, } /* - * 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 = 0; + *reset_state = true; /* AS type */ if (as_type != NULL) @@ -1416,7 +1433,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("INCREMENT must not be zero"))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1428,7 +1445,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, { seqform->seqcycle = boolVal(is_cycled->arg); Assert(BoolIsValid(seqform->seqcycle)); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1439,7 +1456,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (max_value != NULL && max_value->arg) { seqform->seqmax = defGetInt64(max_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || max_value != NULL || reset_max_value) { @@ -1455,7 +1472,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmax = -1; /* descending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate maximum value. No need to check INT8 as seqmax is an int64 */ @@ -1471,7 +1488,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (min_value != NULL && min_value->arg) { seqform->seqmin = defGetInt64(min_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || min_value != NULL || reset_min_value) { @@ -1487,7 +1504,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmin = 1; /* ascending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate minimum value. No need to check INT8 as seqmin is an int64 */ @@ -1538,30 +1555,30 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (restart_value != NULL) { if (restart_value->arg != NULL) - seqdataform->last_value = defGetInt64(restart_value); + *last_value = defGetInt64(restart_value); else - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; - seqdataform->log_cnt = 0; + *last_value = seqform->seqstart; + *is_called = false; + *reset_state = true; } else if (isInit) { - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; + *last_value = seqform->seqstart; + *is_called = false; } /* 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))); /* CACHE */ @@ -1573,7 +1590,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("CACHE (%lld) must be greater than zero", (long long) seqform->seqcache))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { -- 2.45.2 [text/x-diff] v9-0002-Integrate-addition-of-attributes-for-sequences-wi.patch (11.1K, ../../[email protected]/3-v9-0002-Integrate-addition-of-attributes-for-sequences-wi.patch) download | inline diff: From d1527477226df9df7d638e032aa331181e4fcc6d Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 19 Jan 2024 15:00:14 +0900 Subject: [PATCH v9 2/7] 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 1c314cd907..a623cb1612 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2347,6 +2347,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/sequence.c index ed2cd2b1fc..158ea79c66 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 = NIL; + List *atcmds = 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 = NIL; for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) { ColumnDef *coldef = NULL; @@ -198,7 +200,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) coldef->is_not_null = true; null[i - 1] = false; - stmt->tableElts = lappend(stmt->tableElts, coldef); + elts = lappend(elts, coldef); } stmt->relation = seq->sequence; @@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; + /* + * Initial relation has no attributes, these are added later. + */ + stmt->tableElts = NIL; address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + tupDesc = RelationGetDescr(rel); /* now initialize the sequence's data */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 96373323b8..ea4c2d0d83 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4492,6 +4492,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 */ @@ -4796,6 +4797,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* Recursion occurs during execution phase */ pass = 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 = 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, @@ -5229,6 +5237,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 = ATExecAddColumn(wqueue, tab, rel, &cmd, cmd->recurse, false, @@ -6391,6 +6400,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 b2ea8125c9..6a65db0910 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1664,7 +1664,11 @@ ProcessUtilitySlow(ParseState *pstate, break; case T_CreateSeqStmt: + EventTriggerAlterTableStart(parsetree); address = DefineSequence(pstate, (CreateSeqStmt *) parsetree); + /* stashed internally */ + commandCollected = true; + EventTriggerAlterTableEnd(); break; case T_AlterSeqStmt: diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/test/modules/test_ddl_deparse/expected/alter_table.out index 6daa186a84..24b2179455 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 of sequence parent_b_seq NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type ADD COLUMN (and recurse) desc column b of table parent NOTICE: DDL test: type simple, tag ALTER SEQUENCE @@ -76,7 +79,10 @@ NOTICE: subcommand: type SET NOT NULL desc column a of table parent NOTICE: subcommand: type SET NOT NULL desc column a of table child NOTICE: subcommand: type SET NOT NULL desc column a of table grandchild 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 of 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 table parent diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out 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 of 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 2178ce83e9..2be95f99f8 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 of 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 of 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 of sequence datatype_table_is_small_seq NOTICE: DDL test: type simple, tag CREATE TABLE NOTICE: DDL test: type simple, tag CREATE INDEX NOTICE: DDL test: type simple, tag CREATE INDEX diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c index 2758ae82d7..280642e81c 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 = "ADD COLUMN"; break; + case AT_AddColumnToSequence: + strtype = "ADD COLUMN TO SEQUENCE"; + break; case AT_AddColumnToView: strtype = "ADD COLUMN TO VIEW"; break; -- 2.45.2 [text/x-diff] v9-0003-Refactor-code-for-in-core-local-sequences.patch (54.5K, ../../[email protected]/4-v9-0003-Refactor-code-for-in-core-local-sequences.patch) download | inline diff: From fd9ddc51d7a16097633d07902d27da7924b29c08 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Thu, 25 Apr 2024 16:49:47 +0900 Subject: [PATCH v9 3/7] Refactor code for in-core "local" sequences This commit restructures the code of in-core sequences into a new set of files: - seqdesc.c is renamed to seqlocaldesc.c. - seqlocalxlog.c to the code in the WAL replay logic. - seqlocalam.c to store a set of routines called from sequence.c, finishing the separation between the main sequence logic and the in-core sequences. - seqlocalam.h to store the AM-specific structures and routine related to the in-core sequences. WAL records are renamed to "SequenceLocal" with structures, variables and file structures mapping to that. --- src/include/access/rmgrlist.h | 2 +- src/include/access/seqlocalam.h | 58 ++ src/backend/access/rmgrdesc/Makefile | 2 +- src/backend/access/rmgrdesc/meson.build | 2 +- .../rmgrdesc/{seqdesc.c => seqlocaldesc.c} | 20 +- src/backend/access/sequence/Makefile | 2 +- src/backend/access/sequence/meson.build | 2 + src/backend/access/sequence/seqlocalam.c | 634 ++++++++++++++++++ src/backend/access/sequence/seqlocalxlog.c | 82 +++ src/backend/access/transam/rmgr.c | 1 + src/backend/commands/sequence.c | 634 +----------------- src/bin/pg_waldump/.gitignore | 2 +- src/bin/pg_waldump/rmgrdesc.c | 1 + src/bin/pg_waldump/t/001_basic.pl | 2 +- 14 files changed, 822 insertions(+), 622 deletions(-) create mode 100644 src/include/access/seqlocalam.h rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%) create mode 100644 src/backend/access/sequence/seqlocalam.c create mode 100644 src/backend/access/sequence/seqlocalxlog.c diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h index 78e6b908c6..fa94beb6ed 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, btree_identify, btree_xlog PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL, hash_mask, NULL) PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, 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, NULL, seq_mask, NULL) +PG_RMGR(RM_SEQ_LOCAL_ID, "SequenceLocal", seq_local_redo, seq_local_desc, seq_local_identify, NULL, NULL, seq_local_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, NULL, brin_mask, NULL) PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL, NULL, NULL) diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h new file mode 100644 index 0000000000..225fb9a2cb --- /dev/null +++ b/src/include/access/seqlocalam.h @@ -0,0 +1,58 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.h + * Local sequence access method. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/access/seqlocalam.h + * + *------------------------------------------------------------------------- + */ +#ifndef SEQLOCALAM_H +#define SEQLOCALAM_H + +#include "access/xlogreader.h" +#include "storage/relfilelocator.h" +#include "utils/rel.h" + +/* XLOG stuff */ +#define XLOG_SEQ_LOCAL_LOG 0x00 + +typedef struct xl_seq_local_rec +{ + RelFileLocator locator; + /* SEQUENCE TUPLE DATA FOLLOWS AT THE END */ +} xl_seq_local_rec; + +/* + * The "special area" of a local sequence's buffer page looks like this. + */ +#define SEQ_LOCAL_MAGIC 0x1717 + +typedef struct seq_local_magic +{ + uint32 magic; +} seq_local_magic; + +extern void seq_local_redo(XLogReaderState *record); +extern void seq_local_desc(StringInfo buf, XLogReaderState *record); +extern const char *seq_local_identify(uint8 info); +extern void seq_local_mask(char *page, BlockNumber blkno); + +/* access routines */ +extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last); +extern const char *seq_local_get_table_am(void); +extern void seq_local_init(Relation rel, int64 last_value, bool is_called); +extern void seq_local_setval(Relation rel, int64 next, bool iscalled); +extern void seq_local_reset(Relation rel, int64 startv, bool is_called, + bool reset_state); +extern void seq_local_get_state(Relation rel, int64 *last_value, + bool *is_called); +extern void seq_local_change_persistence(Relation rel, + char newrelpersistence); + +#endif /* SEQLOCALAM_H */ diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile index cd95eec37f..e5900ed77a 100644 --- a/src/backend/access/rmgrdesc/Makefile +++ b/src/backend/access/rmgrdesc/Makefile @@ -24,7 +24,7 @@ OBJS = \ relmapdesc.o \ replorigindesc.o \ rmgrdesc_utils.o \ - seqdesc.o \ + seqlocaldesc.o \ smgrdesc.o \ spgdesc.o \ standbydesc.o \ diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build index e8b7a65fc7..e390d385bf 100644 --- a/src/backend/access/rmgrdesc/meson.build +++ b/src/backend/access/rmgrdesc/meson.build @@ -17,7 +17,7 @@ rmgr_desc_sources = files( 'relmapdesc.c', 'replorigindesc.c', 'rmgrdesc_utils.c', - 'seqdesc.c', + 'seqlocaldesc.c', 'smgrdesc.c', 'spgdesc.c', 'standbydesc.c', diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/seqlocaldesc.c similarity index 64% rename from src/backend/access/rmgrdesc/seqdesc.c rename to src/backend/access/rmgrdesc/seqlocaldesc.c index cf0e02ded5..409c67335e 100644 --- a/src/backend/access/rmgrdesc/seqdesc.c +++ b/src/backend/access/rmgrdesc/seqlocaldesc.c @@ -1,44 +1,44 @@ /*------------------------------------------------------------------------- * - * seqdesc.c - * rmgr descriptor routines for commands/sequence.c + * seqlocaldesc.c + * rmgr descriptor routines for sequence/seqlocal.c * * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * src/backend/access/rmgrdesc/seqdesc.c + * src/backend/access/rmgrdesc/seqlocaldesc.c * *------------------------------------------------------------------------- */ #include "postgres.h" -#include "commands/sequence.h" +#include "access/seqlocalam.h" void -seq_desc(StringInfo buf, XLogReaderState *record) +seq_local_desc(StringInfo buf, XLogReaderState *record) { char *rec = XLogRecGetData(record); uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - xl_seq_rec *xlrec = (xl_seq_rec *) rec; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) rec; - if (info == XLOG_SEQ_LOG) + if (info == XLOG_SEQ_LOCAL_LOG) appendStringInfo(buf, "rel %u/%u/%u", xlrec->locator.spcOid, xlrec->locator.dbOid, xlrec->locator.relNumber); } const char * -seq_identify(uint8 info) +seq_local_identify(uint8 info) { const char *id = NULL; switch (info & ~XLR_INFO_MASK) { - case XLOG_SEQ_LOG: - id = "LOG"; + case XLOG_SEQ_LOCAL_LOG: + id = "SEQ_LOCAL_LOG"; break; } diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile index 9f9d31f542..a15ceec1c0 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index 1843aed38d..4d24f900b6 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -1,5 +1,7 @@ # Copyright (c) 2022-2024, PostgreSQL Global Development Group backend_sources += files( + 'seqlocalam.c', + 'seqlocalxlog.c', 'sequence.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c new file mode 100644 index 0000000000..b0a0d72966 --- /dev/null +++ b/src/backend/access/sequence/seqlocalam.c @@ -0,0 +1,634 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.c + * Local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalam.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/multixact.h" +#include "access/seqlocalam.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 SEQ_LOCAL_LOG_VALS 32 + +/* Format of tuples stored in heap table associated to local sequences */ +typedef struct FormData_pg_seq_local_data +{ + int64 last_value; + int64 log_cnt; + bool is_called; +} FormData_pg_seq_local_data; + +typedef FormData_pg_seq_local_data *Form_pg_seq_local_data; + +/* + * Columns of a local sequence relation + */ +#define SEQ_LOCAL_COL_LASTVAL 1 +#define SEQ_LOCAL_COL_LOG 2 +#define SEQ_LOCAL_COL_CALLED 3 + +#define SEQ_LOCAL_COL_FIRSTCOL SEQ_LOCAL_COL_LASTVAL +#define SEQ_LOCAL_COL_LASTCOL SEQ_LOCAL_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_LOCAL_LOG_VALS 32 + +static Form_pg_seq_local_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 tuple + * + * *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_seq_local_data +read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + seq_local_magic *sm; + Form_pg_seq_local_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SEQ_LOCAL_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_pg_seq_local_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 == RELPERSISTENCE_UNLOGGED) + { + SMgrRelation srel; + + srel = 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 content + */ +static void +fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + seq_local_magic *sm; + OffsetNumber offnum; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + sm->magic = SEQ_LOCAL_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) tuple->t_data, tuple->t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_nextval() + * + * Allocate a new value for a local sequence, based on the sequence + * configuration. + */ +int64 +seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + int64 result; + int64 fetch; + int64 next; + int64 rescnt = 0; + int64 log; + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + Page page; + bool logit = false; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + page = BufferGetPage(buf); + + *last = next = result = seq->last_value; + fetch = cache; + log = 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_LOCAL_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 = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = true; + } + else + { + XLogRecPtr redoptr = GetRedoRecPtr(); + + if (PageGetLSN(page) <= redoptr) + { + /* last update of seq was before checkpoint */ + fetch = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = 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 >= 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 = minv; + } + else + next += incby; + } + else + { + /* descending sequence */ + if ((minv < 0 && next < minv - incby) || + (minv >= 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 = maxv; + } + else + next += incby; + } + fetch--; + if (rescnt < cache) + { + log--; + rescnt++; + *last = next; + if (rescnt == 1) /* if it's first result - */ + result = next; /* it's what to return */ + } + } + + log -= fetch; /* adjust for any unfetched numbers */ + Assert(log >= 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_seq_local_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 = next; + seq->is_called = true; + seq->log_cnt = 0; + + xlrec.locator = rel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + /* Now update sequence tuple to the intended final state */ + seq->last_value = *last; /* last fetched number */ + seq->is_called = true; + seq->log_cnt = log; /* how much is logged */ + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + return result; +} + +/* + * seq_local_get_table_am() + * + * Return the table access method used by this sequence. + */ +const char * +seq_local_get_table_am(void) +{ + return "heap"; +} + +/* + * seq_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 + * the information guessed from pg_sequences. This is the first tuple + * inserted after the relation has been created, filling in its heap + * table. + */ +void +seq_local_init(Relation rel, int64 last_value, bool is_called) +{ + Datum value[SEQ_LOCAL_COL_LASTCOL]; + bool null[SEQ_LOCAL_COL_LASTCOL]; + List *elts = NIL; + List *atcmds = NIL; + ListCell *lc; + TupleDesc tupdesc; + HeapTuple tuple; + + /* + * Create relation (and fill value[] and null[] for the initial tuple). + */ + for (int i = SEQ_LOCAL_COL_FIRSTCOL; i <= SEQ_LOCAL_COL_LASTCOL; i++) + { + ColumnDef *coldef = NULL; + + switch (i) + { + case SEQ_LOCAL_COL_LASTVAL: + coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatumFast(last_value); + break; + case SEQ_LOCAL_COL_LOG: + coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatum(0); + break; + case SEQ_LOCAL_COL_CALLED: + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + value[i - 1] = BoolGetDatum(is_called); + break; + } + + coldef->is_not_null = true; + null[i - 1] = false; + elts = lappend(elts, coldef); + } + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, value, null); + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_setval() + * + * Callback for setval(). + */ +void +seq_local_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->last_value = next; /* last fetched number */ + seq->is_called = iscalled; + seq->log_cnt = 0; + + MarkBufferDirty(buf); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel)) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + Page page = BufferGetPage(buf); + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_reset() + * + * Perform a hard reset on the local sequence, rewriting its heap data + * entirely. + */ +void +seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) +{ + Form_pg_seq_local_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 = 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 = (Form_pg_seq_local_data) GETSTRUCT(tuple); + seq->last_value = startv; + seq->is_called = is_called; + if (reset_state) + seq->log_cnt = 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 == InvalidTransactionId); + Assert(rel->rd_rel->relminmxid == InvalidMultiXactId); + + /* + * Insert the modified tuple into the new storage file. + */ + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_get_state() + * + * Retrieve the state of a local sequence. + */ +void +seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + *last_value = seq->last_value; + *is_called = seq->is_called; + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_change_persistence() + * + * Persistence change for the local sequence Relation. + */ +void +seq_local_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/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c new file mode 100644 index 0000000000..db0ad969db --- /dev/null +++ b/src/backend/access/sequence/seqlocalxlog.c @@ -0,0 +1,82 @@ +/*------------------------------------------------------------------------- + * + * seqlocalxlog.c + * WAL replay logic for local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalxlog.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/bufmask.h" +#include "access/seqlocalam.h" +#include "access/xlogutils.h" +#include "storage/block.h" + +/* + * Mask a Sequence page before performing consistency checks on it. + */ +void +seq_local_mask(char *page, BlockNumber blkno) +{ + mask_page_lsn_and_checksum(page); + + mask_unused_space(page); +} + +void +seq_local_redo(XLogReaderState *record) +{ + XLogRecPtr lsn = record->EndRecPtr; + uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; + Buffer buffer; + Page page; + Page localpage; + char *item; + Size itemsz; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record); + seq_local_magic *sm; + + if (info != XLOG_SEQ_LOCAL_LOG) + elog(PANIC, "seq_redo: unknown op code %u", info); + + buffer = XLogInitBufferForRedo(record, 0); + page = (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 = (Page) palloc(BufferGetPageSize(buffer)); + + PageInit(localpage, BufferGetPageSize(buffer), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(localpage); + sm->magic = SEQ_LOCAL_MAGIC; + + item = (char *) xlrec + sizeof(xl_seq_local_rec); + itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec); + + if (PageAddItem(localpage, (Item) item, itemsz, + FirstOffsetNumber, false, false) == InvalidOffsetNumber) + elog(PANIC, "seq_local_redo: failed to add item to page"); + + PageSetLSN(localpage, lsn); + + memcpy(page, localpage, BufferGetPageSize(buffer)); + MarkBufferDirty(buffer); + UnlockReleaseBuffer(buffer); + + pfree(localpage); +} diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c index 1b7499726e..cc92268937 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/seqlocalam.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/sequence.c index 158ea79c66..1cad06da66 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,6 +16,7 @@ #include "access/bufmask.h" #include "access/htup_details.h" +#include "access/seqlocalam.h" #include "access/multixact.h" #include "access/relation.h" #include "access/sequence.h" @@ -50,23 +51,6 @@ #include "utils/varlena.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 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 current * session. This is needed to hold onto nextval/currval state. (We can't @@ -96,13 +80,9 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */ */ static SeqTableData *last_used_seq = NULL; -static void fill_seq_with_data(Relation rel, HeapTuple tuple); -static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber 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_identity, 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 = NIL; - List *atcmds = NIL; - ListCell *lc; Datum pgs_values[Natts_pg_sequence]; bool pgs_nulls[Natts_pg_sequence]; - int i; /* * 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); - /* - * Create relation (and fill value[] and null[] for the tuple) - */ - for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) - { - ColumnDef *coldef = NULL; - - switch (i) - { - case SEQ_COL_LASTVAL: - coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(last_value); - break; - case SEQ_COL_LOG: - coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatum((int64) 0); - break; - case SEQ_COL_CALLED: - coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); - value[i - 1] = BoolGetDatum(false); - break; - } - - coldef->is_not_null = true; - null[i - 1] = false; - - elts = lappend(elts, coldef); - } - stmt->relation = seq->sequence; stmt->inhRelations = NIL; stmt->constraints = NIL; @@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) */ stmt->tableElts = NIL; + /* + * Initial relation has no attributes, these can be added later via the + * "init" AM callback. + */ + stmt->tableElts = NIL; + address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); - /* Add all the attributes to the sequence */ - foreach(lc, elts) - { - AlterTableCmd *atcmd; - - atcmd = makeNode(AlterTableCmd); - atcmd->subtype = AT_AddColumnToSequence; - atcmd->def = (Node *) lfirst(lc); - atcmds = lappend(atcmds, atcmd); - } - - /* - * No recursion needed. Note that EventTriggerAlterTableStart() should - * have been called. - */ - AlterTableInternal(RelationGetRelid(rel), atcmds, false); - CommandCounterIncrement(); - - tupDesc = RelationGetDescr(rel); - - /* now initialize the sequence's data */ - tuple = heap_form_tuple(tupDesc, value, null); - fill_seq_with_data(rel, tuple); + /* now initialize the sequence table structure and its data */ + seq_local_init(rel, last_value, is_called); /* 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); pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid)); if (!HeapTupleIsValid(pgstuple)) @@ -315,40 +240,8 @@ ResetSequence(Oid seq_relid) startv = pgsform->seqstart; ReleaseSysCache(pgstuple); - /* - * Copy the existing sequence tuple. - */ - tuple = 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 = (Form_pg_sequence_data) GETSTRUCT(tuple); - seq->last_value = startv; - seq->is_called = false; - seq->log_cnt = 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 == InvalidTransactionId); - Assert(seq_rel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - fill_seq_with_data(seq_rel, tuple); + /* Sequence state is forcibly reset here. */ + seq_local_reset(seq_rel, startv, false, true); /* 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); } -/* - * 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 == RELPERSISTENCE_UNLOGGED) - { - SMgrRelation srel; - - srel = 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 content - */ -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 = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, - EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); - Assert(BufferGetBlockNumber(buf) == 0); - - page = BufferGetPage(buf); - - PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(page); - sm->magic = SEQ_MAGIC; - - /* Now insert sequence tuple */ - - /* - * Since VACUUM does not process sequences, we have to force the tuple to - * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, - InvalidOffsetNumber, false, false); - if (offnum != FirstOffsetNumber) - elog(ERROR, "failed to add sequence tuple to page"); - - /* XLOG stuff */ - if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = rel->rd_locator; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) tuple->t_data, tuple->t_len); - - recptr = 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 = false; bool is_called; int64 last_value; - HeapTuple newdatatuple; /* Open and lock sequence, and check for ownership along the way. */ relid = RangeVarGetRelidExtended(stmt->sequence, @@ -507,16 +296,8 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) seqform = (Form_pg_sequence) GETSTRUCT(seqtuple); - /* 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 = heap_copytuple(&datatuple); - newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); - last_value = newdataform->last_value; - is_called = newdataform->is_called; - - UnlockReleaseBuffer(buf); + /* Read sequence data */ + seq_local_get_state(seqrel, &last_value, &is_called); /* 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(); - /* - * 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 == InvalidTransactionId); - Assert(seqrel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - newdataform->last_value = last_value; - newdataform->is_called = is_called; - if (reset_state) - newdataform->log_cnt = 0; - fill_seq_with_data(seqrel, newdatatuple); + seq_local_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) { SeqTable elm; Relation seqrel; - Buffer buf; - HeapTupleData seqdatatuple; /* * ALTER SEQUENCE acquires this lock earlier. If we're processing an @@ -596,10 +353,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - (void) read_seq_tuple(seqrel, &buf, &seqdatatuple); - RelationSetNewRelfilenumber(seqrel, newrelpersistence); - fill_seq_with_data(seqrel, &seqdatatuple); - UnlockReleaseBuffer(buf); + seq_local_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -662,24 +416,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 = 0; + int64 result; bool cycle; - bool logit = false; /* open and lock sequence */ init_sequence(relid, &elm, &seqrel); @@ -724,105 +469,9 @@ nextval_internal(Oid relid, bool check_permissions) cycle = pgsform->seqcycle; ReleaseSysCache(pgstuple); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - page = BufferGetPage(buf); - - last = next = result = seq->last_value; - fetch = cache; - log = 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 = log = fetch + SEQ_LOG_VALS; - logit = true; - } - else - { - XLogRecPtr redoptr = GetRedoRecPtr(); - - if (PageGetLSN(page) <= redoptr) - { - /* last update of seq was before checkpoint */ - fetch = log = fetch + SEQ_LOG_VALS; - logit = 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 >= 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 = minv; - } - else - next += incby; - } - else - { - /* descending sequence */ - if ((minv < 0 && next < minv - incby) || - (minv >= 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 = maxv; - } - else - next += incby; - } - fetch--; - if (rescnt < cache) - { - log--; - rescnt++; - last = next; - if (rescnt == 1) /* if it's first result - */ - result = next; /* it's what to return */ - } - } - - log -= fetch; /* adjust for any unfetched numbers */ - Assert(log >= 0); + /* retrieve next value from the access method */ + result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -832,69 +481,6 @@ nextval_internal(Oid relid, bool check_permissions) last_used_seq = elm; - /* - * 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 = next; - seq->is_called = true; - seq->log_cnt = 0; - - xlrec.locator = seqrel->rd_locator; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - /* Now update sequence tuple to the intended final state */ - seq->last_value = last; /* last fetched number */ - seq->is_called = true; - seq->log_cnt = log; /* how much is logged */ - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); - sequence_close(seqrel, NoLock); return result; @@ -984,9 +570,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, @@ -1020,9 +603,6 @@ do_setval(Oid relid, int64 next, bool iscalled) */ PreventCommandIfParallelMode("setval()"); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - if ((next < minv) || (next > maxv)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), @@ -1044,37 +624,8 @@ do_setval(Oid relid, int64 next, bool iscalled) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - /* ready to change the on-disk (or really, in-buffer) tuple */ - START_CRIT_SECTION(); - - seq->last_value = next; /* last fetched number */ - seq->is_called = iscalled; - seq->log_cnt = 0; - - MarkBufferDirty(buf); - - /* XLOG stuff */ - if (RelationNeedsWAL(seqrel)) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - Page page = BufferGetPage(buf); - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = seqrel->rd_locator; - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); + /* Call the access method callback */ + seq_local_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1215,62 +766,6 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) } -/* - * Given an opened sequence relation, lock the page buffer and find the tuple - * - * *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 = ReadBuffer(rel, 0); - LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); - - page = BufferGetPage(*buf); - sm = (sequence_magic *) PageGetSpecialPointer(page); - - if (sm->magic != SEQ_MAGIC) - elog(ERROR, "bad magic number in sequence \"%s\": %08X", - RelationGetRelationName(rel), sm->magic); - - lp = PageGetItemId(page, FirstOffsetNumber); - Assert(ItemIdIsNormal(lp)); - - /* Note we currently only bother to set these two fields of *seqdatatuple */ - seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); - seqdatatuple->t_len = 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) != InvalidTransactionId) - { - HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); - seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; - seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; - MarkBufferDirtyHint(*buf, true); - } - - seq = (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 @@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; + bool is_called; + int64 last_value; - seq = read_seq_tuple(seqrel, &buf, &seqtuple); + seq_local_get_state(seqrel, &last_value, &is_called); - values[0] = Int64GetDatum(seq->last_value); - values[1] = BoolGetDatum(seq->is_called); - - UnlockReleaseBuffer(buf); + values[0] = Int64GetDatum(last_value); + values[1] = BoolGetDatum(is_called); } else memset(isnull, true, sizeof(isnull)); @@ -1908,17 +1400,9 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; - - seq = read_seq_tuple(seqrel, &buf, &seqtuple); - - is_called = seq->is_called; - result = seq->last_value; - - UnlockReleaseBuffer(buf); + seq_local_get_state(seqrel, &result, &is_called); } + sequence_close(seqrel, NoLock); if (is_called) @@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - -void -seq_redo(XLogReaderState *record) -{ - XLogRecPtr lsn = record->EndRecPtr; - uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - Buffer buffer; - Page page; - Page localpage; - char *item; - Size itemsz; - xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record); - sequence_magic *sm; - - if (info != XLOG_SEQ_LOG) - elog(PANIC, "seq_redo: unknown op code %u", info); - - buffer = XLogInitBufferForRedo(record, 0); - page = (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 = (Page) palloc(BufferGetPageSize(buffer)); - - PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(localpage); - sm->magic = SEQ_MAGIC; - - item = (char *) xlrec + sizeof(xl_seq_rec); - itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec); - - if (PageAddItem(localpage, (Item) item, itemsz, - FirstOffsetNumber, false, false) == 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. */ @@ -1992,14 +1425,3 @@ ResetSequenceCaches(void) last_used_seq = 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..8d1195de26 100644 --- a/src/bin/pg_waldump/.gitignore +++ b/src/bin/pg_waldump/.gitignore @@ -10,13 +10,13 @@ /gistdesc.c /hashdesc.c /heapdesc.c +/seqlocaldesc.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..2fccc7a4e2 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/seqlocalam.h" #include "access/multixact.h" #include "access/nbtxlog.h" #include "access/rmgr.h" diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl index 578e473139..0b911048e1 100644 --- a/src/bin/pg_waldump/t/001_basic.pl +++ b/src/bin/pg_waldump/t/001_basic.pl @@ -67,7 +67,7 @@ Btree Hash Gin Gist -Sequence +SequenceLocal SPGist BRIN CommitTs -- 2.45.2 [text/x-diff] v9-0004-Sequence-access-methods-backend-support.patch (61.3K, ../../[email protected]/5-v9-0004-Sequence-access-methods-backend-support.patch) download | inline diff: From 1333a9f6fe1253243abb81dc904dd283d8e82561 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Thu, 25 Apr 2024 17:15:20 +0900 Subject: [PATCH v9 4/7] Sequence access methods - backend support The "seqlocal" sequence AM is now plugged in as a handler in the relcache, and a set of callbacks in sequenceam.h. --- src/include/access/seqlocalam.h | 15 -- src/include/access/sequenceam.h | 181 ++++++++++++++++++ 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/meson.build | 1 + src/backend/access/sequence/seqlocalam.c | 41 +++- 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 | 23 +-- 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/psql/describe.c | 2 + src/bin/psql/tab-complete.c | 4 +- src/test/regress/expected/create_am.out | 55 ++++-- src/test/regress/expected/opr_sanity.out | 12 ++ src/test/regress/expected/psql.out | 96 +++++----- src/test/regress/sql/create_am.sql | 24 ++- src/test/regress/sql/opr_sanity.sql | 10 + src/tools/pgindent/typedefs.list | 5 +- 38 files changed, 683 insertions(+), 170 deletions(-) create mode 100644 src/include/access/sequenceam.h create mode 100644 src/backend/access/sequence/sequenceamapi.c diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h index 225fb9a2cb..21936511ac 100644 --- a/src/include/access/seqlocalam.h +++ b/src/include/access/seqlocalam.h @@ -15,7 +15,6 @@ #include "access/xlogreader.h" #include "storage/relfilelocator.h" -#include "utils/rel.h" /* XLOG stuff */ #define XLOG_SEQ_LOCAL_LOG 0x00 @@ -41,18 +40,4 @@ extern void seq_local_desc(StringInfo buf, XLogReaderState *record); extern const char *seq_local_identify(uint8 info); extern void seq_local_mask(char *page, BlockNumber blkno); -/* access routines */ -extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last); -extern const char *seq_local_get_table_am(void); -extern void seq_local_init(Relation rel, int64 last_value, bool is_called); -extern void seq_local_setval(Relation rel, int64 next, bool iscalled); -extern void seq_local_reset(Relation rel, int64 startv, bool is_called, - bool reset_state); -extern void seq_local_get_state(Relation rel, int64 *last_value, - bool *is_called); -extern void seq_local_change_persistence(Relation rel, - char newrelpersistence); - #endif /* SEQLOCALAM_H */ diff --git a/src/include/access/sequenceam.h b/src/include/access/sequenceam.h new file mode 100644 index 0000000000..ac48c8b468 --- /dev/null +++ b/src/include/access/sequenceam.h @@ -0,0 +1,181 @@ +/*------------------------------------------------------------------------- + * + * sequenceam.h + * POSTGRES sequence access method definitions. + * + * + * Portions Copyright (c) 1996-2024, 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 "seqlocal" + +/* 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 value + * allocated. The result returned is the next value of the sequence computed. + */ +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); + +#endif /* SEQUENCEAM_H */ diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat index db87490282..ba9766fae4 100644 --- a/src/include/catalog/pg_am.dat +++ b/src/include/catalog/pg_am.dat @@ -15,6 +15,9 @@ { oid => '2', oid_symbol => 'HEAP_TABLE_AM_OID', descr => 'heap table access method', amname => 'heap', amhandler => 'heap_tableam_handler', amtype => 't' }, +{ oid => '8051', oid_symbol => 'LOCAL_SEQUENCE_AM_OID', + descr => 'local sequence access method', + amname => 'seqlocal', amhandler => 'seq_local_sequenceam_handler', amtype => 's' }, { oid => '403', oid_symbol => 'BTREE_AM_OID', descr => 'b-tree index access method', amname => 'btree', amhandler => 'bthandler', amtype => '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 */ #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) == RELKIND_TOASTVALUE || \ (relkind) == RELKIND_MATVIEW) +/* + * Relation kinds with a sequence access method (rd_sequenceam). + */ +#define RELKIND_HAS_SEQUENCE_AM(relkind) \ + ((relkind) == RELKIND_SEQUENCE) + extern int errdetail_relkind_not_supported(char relkind); #endif /* EXPOSE_TO_CLIENT_CODE */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 05fcbf7515..f1ab5fd1b7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -913,6 +913,12 @@ prorettype => 'table_am_handler', proargtypes => 'internal', prosrc => 'heap_tableam_handler' }, +# Sequence access method handlers +{ oid => '8209', descr => 'local sequence access method handler', + proname => 'seq_local_sequenceam_handler', provolatile => 'v', + prorettype => 'sequence_am_handler', proargtypes => 'internal', + prosrc => 'seq_local_sequenceam_handler' }, + # Index access method handlers { oid => '330', descr => 'btree index access method handler', proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler', @@ -7740,6 +7746,13 @@ { oid => '327', descr => 'I/O', proname => 'index_am_handler_out', prorettype => 'cstring', proargtypes => 'index_am_handler', prosrc => 'index_am_handler_out' }, +{ oid => '8207', descr => 'I/O', + proname => 'sequence_am_handler_in', proisstrict => 'f', + prorettype => 'sequence_am_handler', proargtypes => 'cstring', + prosrc => 'sequence_am_handler_in' }, +{ oid => '8208', descr => 'I/O', + proname => 'sequence_am_handler_out', prorettype => 'cstring', + proargtypes => 'sequence_am_handler', prosrc => 'sequence_am_handler_out' }, { oid => '3311', descr => 'I/O', proname => 'tsm_handler_in', proisstrict => 'f', prorettype => 'tsm_handler', proargtypes => 'cstring', prosrc => 'tsm_handler_in' }, diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat index ceff66ccde..0eba013687 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -627,6 +627,12 @@ typcategory => 'P', typinput => 'index_am_handler_in', typoutput => 'index_am_handler_out', typreceive => '-', typsend => '-', typalign => 'i' }, +{ oid => '8210', + descr => 'pseudo-type for the result of a sequence AM handler function', + typname => 'sequence_am_handler', typlen => '4', typbyval => 't', + typtype => 'p', typcategory => 'P', typinput => 'sequence_am_handler_in', + typoutput => 'sequence_am_handler_out', typreceive => '-', typsend => '-', + typalign => 'i' }, { oid => '3310', descr => 'pseudo-type for the result of a tablesample method function', typname => 'tsm_handler', typlen => '4', typbyval => 't', typtype => '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); diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.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" -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); -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 = [ '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 a623cb1612..99a48777b4 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3139,6 +3139,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; diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 5813dba0a2..f4a49969b8 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, 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 **extra, + 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; + /* + * 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/sequence/Makefile index a15ceec1c0..62006165a1 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = seqlocalam.o seqlocalxlog.o sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o sequenceamapi.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index 4d24f900b6..ea91990552 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -4,4 +4,5 @@ backend_sources += files( 'seqlocalam.c', 'seqlocalxlog.c', 'sequence.c', + 'sequenceamapi.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c index b0a0d72966..2e524c9e99 100644 --- a/src/backend/access/sequence/seqlocalam.c +++ b/src/backend/access/sequence/seqlocalam.c @@ -17,6 +17,7 @@ #include "access/multixact.h" #include "access/seqlocalam.h" +#include "access/sequenceam.h" #include "access/xact.h" #include "access/xloginsert.h" #include "access/xlogutils.h" @@ -24,6 +25,7 @@ #include "commands/tablecmds.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "utils/builtins.h" /* @@ -230,10 +232,10 @@ fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) * Allocate a new value for a local sequence, based on the sequence * configuration. */ -int64 +static int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last) + int64 minv, int64 cache, bool cycle, + int64 *last) { int64 result; int64 fetch; @@ -417,7 +419,7 @@ seq_local_nextval(Relation rel, int64 incby, int64 maxv, * * Return the table access method used by this sequence. */ -const char * +static const char * seq_local_get_table_am(void) { return "heap"; @@ -432,7 +434,7 @@ seq_local_get_table_am(void) * inserted after the relation has been created, filling in its heap * table. */ -void +static void seq_local_init(Relation rel, int64 last_value, bool is_called) { Datum value[SEQ_LOCAL_COL_LASTCOL]; @@ -499,7 +501,7 @@ seq_local_init(Relation rel, int64 last_value, bool is_called) * * Callback for setval(). */ -void +static void seq_local_setval(Relation rel, int64 next, bool iscalled) { Buffer buf; @@ -547,7 +549,7 @@ seq_local_setval(Relation rel, int64 next, bool iscalled) * Perform a hard reset on the local sequence, rewriting its heap data * entirely. */ -void +static void seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) { Form_pg_seq_local_data seq; @@ -600,7 +602,7 @@ seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) * * Retrieve the state of a local sequence. */ -void +static void seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) { Buffer buf; @@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) * * Persistence change for the local sequence Relation. */ -void +static void seq_local_change_persistence(Relation rel, char newrelpersistence) { Buffer buf; @@ -632,3 +634,24 @@ seq_local_change_persistence(Relation rel, char newrelpersistence) fill_seq_with_data(rel, &seqdatatuple); UnlockReleaseBuffer(buf); } + +/* ------------------------------------------------------------------------ + * Definition of the local sequence access method. + * ------------------------------------------------------------------------ + */ +static const SequenceAmRoutine seq_local_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = seq_local_get_table_am, + .init = seq_local_init, + .nextval = seq_local_nextval, + .setval = seq_local_setval, + .reset = seq_local_reset, + .get_state = seq_local_get_state, + .change_persistence = seq_local_change_persistence +}; + +Datum +seq_local_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&seq_local_methods); +} diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/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 sequences - * (in contrast to other relation types like indexes). + * (in contrast to other relation types like indexes) that are independent + * of individual sequence access methods. * *------------------------------------------------------------------------- */ diff --git a/src/backend/access/sequence/sequenceamapi.c b/src/backend/access/sequence/sequenceamapi.c new file mode 100644 index 0000000000..dd1a60d827 --- /dev/null +++ b/src/backend/access/sequence/sequenceamapi.c @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------- + * + * sequenceamapi.c + * general sequence access method routines + * + * Portions Copyright (c) 1996-2024, 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 = 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 = OidFunctionCall0(amhandler); + routine = (SequenceAmRoutine *) DatumGetPointer(datum); + + if (routine == NULL || !IsA(routine, SequenceAmRoutine)) + elog(ERROR, "sequence access method handler %u did not return a SequenceAmRoutine 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 != NULL); + Assert(routine->init != NULL); + Assert(routine->nextval != NULL); + Assert(routine->setval != NULL); + Assert(routine->reset != NULL); + Assert(routine->get_state != NULL); + Assert(routine->change_persistence != 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 = SearchSysCache1(AMOID, + ObjectIdGetDatum(amoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", amoid); + aform = (Form_pg_am) GETSTRUCT(tuple); + Assert(aform->amtype == AMTYPE_SEQUENCE); + amhandleroid = 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 == '\0') + { + GUC_check_errdetail("%s cannot be empty.", + "default_sequence_access_method"); + return false; + } + + if (strlen(*newval) >= 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 != InvalidOid) + { + if (!OidIsValid(get_sequence_am_oid(*newval, true))) + { + /* + * When source == 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 == 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 78e59384d1..a5c1e9df73 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -1472,8 +1472,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 != RELKIND_TOASTVALUE) + if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != 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 @@ #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); } +/* + * 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 = INDEX_AM_HANDLEROID; break; + case AMTYPE_SEQUENCE: + expectedType = SEQUENCE_AM_HANDLEROID; + break; case AMTYPE_TABLE: expectedType = TABLE_AM_HANDLEROID; break; diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 1cad06da66..a65e93bbee 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,10 +16,10 @@ #include "access/bufmask.h" #include "access/htup_details.h" -#include "access/seqlocalam.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 = NIL; stmt->constraints = NIL; stmt->options = NIL; + stmt->accessMethod = seq->accessMethod ? pstrdup(seq->accessMethod) : NULL; stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; @@ -173,7 +174,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) rel = sequence_open(seqoid, AccessExclusiveLock); /* now initialize the sequence table structure and its data */ - seq_local_init(rel, last_value, is_called); + sequence_init(rel, last_value, is_called); /* process OWNED BY if given */ if (owned_by) @@ -241,7 +242,7 @@ ResetSequence(Oid seq_relid) ReleaseSysCache(pgstuple); /* Sequence state is forcibly reset here. */ - seq_local_reset(seq_rel, startv, false, true); + sequence_reset(seq_rel, startv, false, true); /* 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 = (Form_pg_sequence) GETSTRUCT(seqtuple); /* Read sequence data */ - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); /* 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(); - seq_local_reset(seqrel, last_value, is_called, reset_state); + sequence_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -353,7 +354,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - seq_local_change_persistence(seqrel, newrelpersistence); + sequence_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -470,8 +471,8 @@ nextval_internal(Oid relid, bool check_permissions) ReleaseSysCache(pgstuple); /* retrieve next value from the access method */ - result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, - &last); + result = sequence_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -625,7 +626,7 @@ do_setval(Oid relid, int64 next, bool iscalled) GetTopTransactionId(); /* Call the access method callback */ - seq_local_setval(seqrel, next, iscalled); + sequence_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS) bool is_called; int64 last_value; - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); values[0] = Int64GetDatum(last_value); values[1] = BoolGetDatum(is_called); @@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - seq_local_get_state(seqrel, &result, &is_called); + sequence_get_state(seqrel, &result, &is_called); } sequence_close(seqrel, NoLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index ea4c2d0d83..65f9336b86 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" @@ -949,14 +950,18 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, } /* - * 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 != NULL) { - Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE); - accessMethodId = get_table_am_oid(stmt->accessMethod, false); + Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE || + RELKIND_HAS_SEQUENCE_AM(relkind)); + if (RELKIND_HAS_SEQUENCE_AM(relkind)) + accessMethodId = get_sequence_am_oid(stmt->accessMethod, false); + else + accessMethodId = get_table_am_oid(stmt->accessMethod, false); } else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE) { @@ -969,6 +974,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId)) accessMethodId = get_table_am_oid(default_table_access_method, false); } + else if (RELKIND_HAS_SEQUENCE_AM(relkind)) + { + accessMethodId = get_sequence_am_oid(default_sequence_access_method, false); + } /* * 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 = \ 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 = 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 = 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 4aa8646af7..835d680c87 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -390,6 +390,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <str> copy_file_name access_method_clause attr_name table_access_method_clause name cursor_name file_name + sequence_access_method_clause cluster_index_specification %type <list> func_name handler_name qual_Op qual_all_Op subquery_Op @@ -4883,23 +4884,26 @@ RefreshMatViewStmt: CreateSeqStmt: CREATE OptTemp SEQUENCE qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); - $4->relpersistence = $2; n->sequence = $4; n->options = $5; + n->accessMethod = $6; n->ownerId = InvalidOid; n->if_not_exists = false; $$ = (Node *) n; } | CREATE OptTemp SEQUENCE IF_P NOT EXISTS qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); $7->relpersistence = $2; n->sequence = $7; n->options = $8; + n->accessMethod = $9; n->ownerId = InvalidOid; n->if_not_exists = true; $$ = (Node *) n; @@ -4936,6 +4940,11 @@ OptParenthesizedSeqOptList: '(' SeqOptList ')' { $$ = $2; } | /*EMPTY*/ { $$ = NIL; } ; +sequence_access_method_clause: + USING name { $$ = $2; } + | /*EMPTY*/ { $$ = NULL; } + ; + SeqOptList: SeqOptElem { $$ = list_make1($1); } | SeqOptList SeqOptElem { $$ = lappend($1, $2); } ; @@ -5939,6 +5948,7 @@ CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name am_type: INDEX { $$ = AMTYPE_INDEX; } + | SEQUENCE { $$ = AMTYPE_SEQUENCE; } | TABLE { $$ = AMTYPE_TABLE; } ; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 1e15ce10b4..88fe3661c4 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" @@ -489,6 +490,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, seqstmt->sequence = makeRangeVar(snamespace, sname, -1); seqstmt->sequence->relpersistence = seqpersistence; seqstmt->options = seqoptions; + seqstmt->accessMethod = NULL; /* * 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/pseudotypes.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/relcache.c index c326f687eb..a47ed6e52a 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" @@ -301,6 +303,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); @@ -1204,8 +1207,7 @@ retry: if (relation->rd_rel->relkind == RELKIND_INDEX || relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) RelationInitIndexAccessInfo(relation); - else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || - relation->rd_rel->relkind == RELKIND_SEQUENCE) + else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)) RelationInitTableAccessMethod(relation); else if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { @@ -1214,6 +1216,8 @@ retry: * inherit. */ } + else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind)) + RelationInitSequenceAccessMethod(relation); else Assert(relation->rd_rel->relam == InvalidOid); @@ -1810,17 +1814,9 @@ RelationInitTableAccessMethod(Relation relation) HeapTuple tuple; Form_pg_am aform; - if (relation->rd_rel->relkind == 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 == InvalidOid); - relation->rd_amhandler = 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. @@ -1851,6 +1847,49 @@ RelationInitTableAccessMethod(Relation relation) InitTableAmRoutine(relation); } +/* + * 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 != InvalidOid); + relation->rd_amhandler = GetSequenceAmRoutineId(relation->rd_rel->relam); + + /* + * Now we can fetch the sequence AM's API struct. + */ + relation->rd_sequenceam = GetSequenceAmRoutine(relation->rd_amhandler); + + /* + * From the sequence AM, set its expected table access method. + */ + tableam_name = sequence_get_table_am(relation); + tableam_oid = get_table_am_oid(tableam_name, false); + + tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(tableam_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", + tableam_oid); + aform = (Form_pg_am) GETSTRUCT(tuple); + tableam_handler = aform->amhandler; + ReleaseSysCache(tuple); + + relation->rd_tableam = GetTableAmRoutine(tableam_handler); +} + /* * formrdesc * @@ -3711,14 +3750,17 @@ RelationBuildLocalRelation(const char *relname, rel->rd_rel->relam = accessmtd; /* - * 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); - if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(relkind)) RelationInitTableAccessMethod(rel); + else if (relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); /* * Okay to insert into the relcache hash table. @@ -4336,13 +4378,21 @@ RelationCacheInitializePhase3(void) /* Reload tableam data if needed */ if (relation->rd_tableam == NULL && - (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE)) + (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))) { RelationInitTableAccessMethod(relation); Assert(relation->rd_tableam != NULL); restart = true; } + else if (relation->rd_sequenceam == NULL && + relation->rd_rel->relkind == RELKIND_SEQUENCE) + { + RelationInitSequenceAccessMethod(relation); + Assert(relation->rd_sequenceam != NULL); + + restart = true; + } /* Release hold on the relation */ RelationDecrementReferenceCount(relation); @@ -6335,8 +6385,10 @@ load_relcache_init_file(bool shared) nailed_rels++; /* Load table AM data */ - if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || rel->rd_rel->relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind)) RelationInitTableAccessMethod(rel); + else if (rel->rd_rel->relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); Assert(rel->rd_index == NULL); Assert(rel->rd_indextuple == NULL); @@ -6348,6 +6400,7 @@ load_relcache_init_file(bool shared) Assert(rel->rd_supportinfo == NULL); Assert(rel->rd_indoption == NULL); Assert(rel->rd_indcollation == NULL); + Assert(rel->rd_sequenceam == NULL); Assert(rel->rd_opcoptions == NULL); } diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 686309db58..75bed693da 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" @@ -4185,6 +4186,17 @@ struct config_string ConfigureNamesString[] = check_default_table_access_method, NULL, NULL }, + { + {"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default sequence access method for new sequences."), + 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/utils/misc/postgresql.conf.sample index 667e0dc40a..879c03c770 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -703,6 +703,7 @@ # error #search_path = '"$user", public' # schema names #row_security = on +#default_sequence_access_method = 'seqlocal' #default_table_access_method = 'heap' #default_tablespace = '' # a tablespace name, '' uses the default #default_toast_compression = 'pglz' # 'pglz' or 'lz4' diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 6a36c91083..449fd3febf 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -161,10 +161,12 @@ describeAccessMethods(const char *pattern, bool verbose) "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")); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index a7ccde6d7d..0352c73e30 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 <name> 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/expected/create_am.out index 35d4cf1d46..f1ad1c583f 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) PARTITION 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 = 'seqlocal'; 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; @@ -519,18 +517,18 @@ FROM pg_class AS pc LEFT JOIN pg_am AS pa ON (pa.oid = pc.relam) WHERE pc.relname LIKE 'tableam_%_heapx' ORDER BY 3, 1, 2; - relkind | amname | relname ----------+--------+----------------------------- - f | | tableam_fdw_heapx - r | heap2 | tableam_parted_1_heapx - r | heap | tableam_parted_2_heapx - p | | tableam_parted_heapx - S | | tableam_seq_heapx - r | heap2 | tableam_tbl_heapx - r | heap2 | tableam_tblas_heapx - m | heap2 | tableam_tblmv_heapx - r | heap2 | tableam_tblselectinto_heapx - v | | tableam_view_heapx + relkind | amname | relname +---------+----------+----------------------------- + f | | tableam_fdw_heapx + r | heap2 | tableam_parted_1_heapx + r | heap | tableam_parted_2_heapx + p | | tableam_parted_heapx + S | seqlocal | tableam_seq_heapx + r | heap2 | tableam_tbl_heapx + r | heap2 | tableam_tblas_heapx + m | heap2 | tableam_tblmv_heapx + r | heap2 | tableam_tblselectinto_heapx + v | | tableam_view_heapx (10 rows) -- don't want to keep those tables, nor the default @@ -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_dump support +-- Checks for sequence access methods +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_local_sequenceam_handler; +-- Create and use sequence +CREATE SEQUENCE test_seqam USING local2; +SELECT nextval('test_seqam'::regclass); + nextval +--------- + 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/expected/opr_sanity.out index 0d734169f1..ea1f1048ea 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1933,6 +1933,18 @@ WHERE p1.oid = a1.amhandler AND a1.amtype = 't' AND -----+--------+-----+--------- (0 rows) +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + oid | amname | oid | proname +-----+--------+-----+--------- +(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 3819bf5e25..f5661cf886 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -5064,31 +5064,33 @@ Indexes: -- check printing info about access methods \dA List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA * List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA h* List of access methods @@ -5113,32 +5115,34 @@ List of access methods \dA: extra argument "bar" ignored \dA+ - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ * - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ h* List of access methods diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql index 825aed325e..472289994e 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 FROM tableam_tbl_heap2; -- CREATE VIEW doesn't support USING CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2; --- 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 tableam_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; --- 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 = 'seqlocal'; 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; -- we intentionally leave the objects created above alive, to verify pg_dump support + +-- Checks for sequence access methods + +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_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 = a1.amhandler AND a1.amtype = 't' AND OR p1.pronargs != 1 OR p1.proargtypes[0] != 'internal'::regtype); +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + -- **************** pg_amop **************** -- Look for illegal values in pg_amop fields diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 5fabb127d7..a69ec1e1a8 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2580,6 +2580,7 @@ SeqScanState SeqTable SeqTableData SeqType +SequenceAmRoutine SequenceItem SerCommitSeqNo SerialControl @@ -3603,6 +3604,7 @@ lineno_t list_sort_comparator local_relopt local_relopts +local_sequence_magic local_source local_ts_iter local_ts_radix_tree @@ -3885,7 +3887,6 @@ scram_state scram_state_enum security_class_t sem_t -sepgsql_context_info_t sequence_magic set_join_pathlist_hook_type set_rel_pathlist_hook_type @@ -4109,6 +4110,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 @@ -4120,7 +4122,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 -- 2.45.2 [text/x-diff] v9-0005-Sequence-access-methods-dump-restore-support.patch (21.7K, ../../[email protected]/6-v9-0005-Sequence-access-methods-dump-restore-support.patch) download | inline diff: From 60c72e4df87cc622ac7e5c9711b184ec20fcdbc2 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Mon, 26 Aug 2024 13:25:19 +0900 Subject: [PATCH v9 5/7] 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 | 47 +++++++++++++++----- 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 | 49 +++++++++++++++++++++ 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, 207 insertions(+), 11 deletions(-) diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index 68ae2970ad..fb944c40b7 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -96,6 +96,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 @@ -186,6 +187,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_backup_archiver.c index 8c20c263c4..0eb5f9de3e 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -174,6 +174,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) dopt->outputSuperuser = ropt->superuser; dopt->outputCreateDB = ropt->createDB; dopt->outputNoOwner = ropt->noOwner; + dopt->outputNoSequenceAm = ropt->noSequenceAm; dopt->outputNoTableAm = ropt->noTableAm; dopt->outputNoTablespaces = ropt->noTablespace; dopt->disable_triggers = ropt->disable_triggers; @@ -1224,6 +1225,7 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId, newToc->tag = pg_strdup(opts->tag); newToc->namespace = opts->namespace ? pg_strdup(opts->namespace) : NULL; newToc->tablespace = opts->tablespace ? pg_strdup(opts->tablespace) : NULL; + newToc->sequenceam = opts->sequenceam ? pg_strdup(opts->sequenceam) : NULL; newToc->tableam = opts->tableam ? pg_strdup(opts->tableam) : NULL; newToc->relkind = opts->relkind; newToc->owner = opts->owner ? pg_strdup(opts->owner) : NULL; @@ -2379,6 +2381,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, AH->currUser = NULL; /* unknown */ AH->currSchema = NULL; /* ditto */ + AH->currSequenceAm = NULL; /* ditto */ AH->currTablespace = NULL; /* ditto */ AH->currTableAm = NULL; /* ditto */ @@ -2608,6 +2611,7 @@ WriteToc(ArchiveHandle *AH) WriteStr(AH, te->copyStmt); WriteStr(AH, te->namespace); WriteStr(AH, te->tablespace); + WriteStr(AH, te->sequenceam); WriteStr(AH, te->tableam); WriteInt(AH, te->relkind); WriteStr(AH, te->owner); @@ -2712,6 +2716,9 @@ ReadToc(ArchiveHandle *AH) if (AH->version >= K_VERS_1_10) te->tablespace = ReadStr(AH); + if (AH->version >= K_VERS_1_17) + te->sequenceam = ReadStr(AH); + if (AH->version >= K_VERS_1_14) te->tableam = ReadStr(AH); @@ -3367,6 +3374,9 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname) free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; + free(AH->currTableAm); AH->currTableAm = NULL; @@ -3529,6 +3539,57 @@ _selectTablespace(ArchiveHandle *AH, const char *tablespace) destroyPQExpBuffer(qry); } +/* + * Set the proper default_sequence_access_method value for the sequence. + */ +static void +_selectSequenceAccessMethod(ArchiveHandle *AH, const char *sequenceam) +{ + RestoreOptions *ropt = AH->public.ropt; + PQExpBuffer cmd; + const char *want, + *have; + + /* do nothing in --no-sequence-access-method mode */ + if (ropt->noSequenceAm) + return; + + have = AH->currSequenceAm; + want = sequenceam; + + if (!want) + return; + + if (have && strcmp(want, have) == 0) + return; + + cmd = createPQExpBuffer(); + appendPQExpBuffer(cmd, + "SET default_sequence_access_method = %s;", + fmtId(want)); + + if (RestoringToDB(AH)) + { + PGresult *res; + + res = PQexec(AH->connection, cmd->data); + + if (!res || PQresultStatus(res) != 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 = pg_strdup(want); +} + /* * Set the proper default_table_access_method value for the table. */ @@ -3738,6 +3799,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData) _becomeOwner(AH, te); _selectOutputSchema(AH, te->namespace); _selectTablespace(AH, te->tablespace); + _selectSequenceAccessMethod(AH, te->sequenceam); if (te->relkind != RELKIND_PARTITIONED_TABLE) _selectTableAccessMethod(AH, te->tableam); @@ -4262,6 +4324,8 @@ restore_toc_entries_prefork(ArchiveHandle *AH, TocEntry *pending_list) AH->currUser = NULL; free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; free(AH->currTablespace); AH->currTablespace = NULL; free(AH->currTableAm); @@ -5001,6 +5065,7 @@ CloneArchive(ArchiveHandle *AH) clone->connCancel = NULL; clone->currUser = NULL; clone->currSchema = NULL; + clone->currSequenceAm = NULL; clone->currTableAm = NULL; clone->currTablespace = NULL; @@ -5060,6 +5125,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_backup_archiver.h index ce5ed1dd39..cdfebcd340 100644 --- a/src/bin/pg_dump/pg_backup_archiver.h +++ b/src/bin/pg_dump/pg_backup_archiver.h @@ -71,10 +71,11 @@ #define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0) /* BLOB METADATA entries * and multiple BLOBS, * relkind */ +#define K_VERS_1_17 MAKE_ARCHIVE_VERSION(1, 17, 0) /* add sequenceam */ /* 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_VERS_REV) @@ -322,6 +323,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 */ @@ -353,6 +355,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 relkind; /* relation kind, only for TABLE tags */ char *owner; @@ -394,6 +397,7 @@ typedef struct _archiveOpts const char *tag; const char *namespace; const char *tablespace; + const char *sequenceam; const char *tableam; char relkind; const char *owner; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 130b80775d..c52f84d4f8 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -134,6 +134,7 @@ typedef struct int64 cache; /* cache size */ int64 last_value; /* last value of sequence */ bool is_called; /* whether nextval advances before returning */ + char *seqam; /* access method of sequence */ } SequenceItem; typedef enum OidOptions @@ -477,6 +478,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}, @@ -1098,6 +1100,7 @@ main(int argc, char **argv) ropt->superuser = dopt.outputSuperuser; ropt->createDB = dopt.outputCreateDB; ropt->noOwner = dopt.outputNoOwner; + ropt->noSequenceAm = dopt.outputNoSequenceAm; ropt->noTableAm = dopt.outputNoTableAm; ropt->noTablespace = dopt.outputNoTablespaces; ropt->disable_triggers = dopt.disable_triggers; @@ -1214,6 +1217,7 @@ help(const char *progname) printf(_(" --no-publications do not dump publications\n")); printf(_(" --no-security-labels do not dump security label assignments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); + printf(_(" --no-sequence-access-method do not sequence table access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); @@ -13112,6 +13116,9 @@ dumpAccessMethod(Archive *fout, const AccessMethodInfo *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; @@ -17393,26 +17400,40 @@ collectSequences(Archive *fout) * * Since version 18, we can gather the sequence data in this query with * pg_get_sequence_data(), but we only do so for non-schema-only dumps. + * + * Access methods for sequences are supported since version 18. */ if (fout->remoteVersion < 100000) return; - else if (fout->remoteVersion < 180000 || - (fout->dopt->schemaOnly && !fout->dopt->sequence_data)) + else if (fout->remoteVersion < 180000) query = "SELECT seqrelid, format_type(seqtypid, NULL), " "seqstart, seqincrement, " "seqmax, seqmin, " "seqcache, seqcycle, " - "NULL, 'f' " + "NULL, 'f', NULL " "FROM pg_catalog.pg_sequence " "ORDER BY seqrelid"; + else if (fout->dopt->schemaOnly && !fout->dopt->sequence_data) + query = "SELECT s.seqrelid, format_type(s.seqtypid, NULL), " + "s.seqstart, s.seqincrement, " + "s.seqmax, s.seqmin, " + "s.seqcache, s.seqcycle, " + "NULL, 'f', a.amname AS seqam " + "FROM pg_catalog.pg_sequence s " + "JOIN pg_class c ON (c.oid = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam) " + "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_get_sequence_data(seqrelid) " + query = "SELECT s.seqrelid, format_type(s.seqtypid, NULL), " + "s.seqstart, s.seqincrement, " + "s.seqmax, s.seqmin, " + "s.seqcache, s.seqcycle, " + "r.last_value, r.is_called, " + "a.amname AS seqam " + "FROM pg_catalog.pg_sequence s " + "JOIN pg_class c ON (c.oid = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam), " + "pg_get_sequence_data(s.seqrelid) r " "ORDER BY seqrelid;"; res = ExecuteSqlQuery(fout, query, PGRES_TUPLES_OK); @@ -17432,6 +17453,10 @@ collectSequences(Archive *fout) 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); + if (!PQgetisnull(res, i, 10)) + sequences[i].seqam = pg_strdup(PQgetvalue(res, i, 10)); + else + sequences[i].seqam = NULL; } PQclear(res); @@ -17503,6 +17528,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) 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); + seq->seqam = NULL; PQclear(res); } @@ -17625,6 +17651,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) ARCHIVE_OPTS(.tag = tbinfo->dobj.name, .namespace = tbinfo->dobj.namespace->dobj.name, .owner = tbinfo->rolname, + .sequenceam = seq->seqam, .description = "SEQUENCE", .section = SECTION_PRE_DATA, .createStmt = query->data, diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index e3ad8fb295..865f11702f 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 = 0; static int disable_triggers = 0; static int if_exists = 0; static int inserts = 0; +static int no_sequence_access_method = 0; static int no_table_access_method = 0; static int no_tablespaces = 0; static int use_setsessauth = 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 assignments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); printf(_(" --no-sync do not wait for changes to be written safely to disk\n")); + printf(_(" --no-sequence-access-method do not dump sequence access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c index df119591cc..081954c5ce 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 = 0; static int if_exists = 0; static int no_data_for_failed_tables = 0; + static int outputNoSequenceAm = 0; static int outputNoTableAm = 0; static int outputNoTablespaces = 0; static int use_setsessauth = 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 = disable_triggers; opts->enable_row_security = enable_row_security; opts->noDataForFailedTables = no_data_for_failed_tables; + opts->noSequenceAm = outputNoSequenceAm; opts->noTableAm = outputNoTableAm; opts->noTablespace = outputNoTablespaces; opts->use_setsessauth = 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 access methods\n")); printf(_(" --no-table-access-method do not restore table access methods\n")); printf(_(" --no-tablespaces do not restore tablespace assignments\n")); printf(_(" --section=SECTION restore named section (pre-data, 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_dump.pl index ab6c830491..c4f3554829 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 = ( 'postgres', ], }, + no_sequence_access_method => { + dump_cmd => [ + 'pg_dump', '--no-sync', + "--file=$tempdir/no_sequence_access_method.sql", + '--no-sequence-access-method', 'postgres', + ], + }, no_table_access_method => { dump_cmd => [ 'pg_dump', '--no-sync', @@ -711,6 +718,7 @@ my %full_runs = ( no_large_objects => 1, no_owner => 1, no_privs => 1, + no_sequence_access_method => 1, no_table_access_method => 1, pg_dumpall_dbprivs => 1, pg_dumpall_exclude => 1, @@ -4524,6 +4532,18 @@ my %tests = ( }, }, + 'CREATE ACCESS METHOD regress_test_sequence_am' => { + create_order => 11, + create_sql => + 'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;', + regexp => qr/^ + \QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;\E + \n/xm, + like => { + %full_runs, section_pre_data => 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 @@ -4552,6 +4572,35 @@ my %tests = ( }, }, + + # 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' => { + create_order => 12, + create_sql => ' + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_0 USING seqlocal; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_1 USING regress_sequence_am; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_2 USING seqlocal;', + regexp => qr/^ + \QSET default_sequence_access_method = 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 => { + %full_runs, %dump_test_schema_runs, section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + no_sequence_access_method => 1, + only_dump_measurement => 1, + }, + }, + 'CREATE MATERIALIZED VIEW regress_pg_dump_matview_am' => { create_order => 13, create_sql => ' diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index ffc29b04fb..0c392a8080 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -1121,6 +1121,23 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + + <para> + This option is ignored when emitting an archive (non-text) output + file. For the archive formats, you can specify the option when you + call <command>pg_restore</command>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml 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 <replaceable class="parameter">PATTERN</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml index b8b27e1719..922f69eb4a 100644 --- a/doc/src/sgml/ref/pg_restore.sgml +++ b/doc/src/sgml/ref/pg_restore.sgml @@ -733,6 +733,17 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> -- 2.45.2 [text/x-diff] v9-0006-Sequence-access-methods-core-documentation.patch (9.5K, ../../[email protected]/7-v9-0006-Sequence-access-methods-core-documentation.patch) download | inline diff: From 4ffc8a56593071162fdf76e0462dd907347cbdb7 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:55:56 +0900 Subject: [PATCH v9 6/7] 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 08173ecb5c..f713ceb378 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8993,6 +8993,22 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-default-sequence-access-method" xreflabel="default_sequence_access_method"> + <term><varname>default_sequence_access_method</varname> (<type>string</type>) + <indexterm> + <primary><varname>default_sequence_access_method</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + This parameter specifies the default sequence access method to use when + creating sequences if the <command>CREATE SEQUENCE</command> + command does not explicitly specify an access method. The default is + <literal>local</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-default-tablespace" xreflabel="default_tablespace"> <term><varname>default_tablespace</varname> (<type>string</type>) <indexterm> diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index a7ff5f8264..07aa3f18c7 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -95,6 +95,7 @@ <!ENTITY planstats SYSTEM "planstats.sgml"> <!ENTITY tableam SYSTEM "tableam.sgml"> <!ENTITY indexam SYSTEM "indexam.sgml"> +<!ENTITY sequenceam SYSTEM "sequenceam.sgml"> <!ENTITY nls SYSTEM "nls.sgml"> <!ENTITY plhandler SYSTEM "plhandler.sgml"> <!ENTITY fdwhandler SYSTEM "fdwhandler.sgml"> diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml index 7be25c5850..cc390e613a 100644 --- a/doc/src/sgml/postgres.sgml +++ b/doc/src/sgml/postgres.sgml @@ -257,6 +257,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 <replaceable class="parameter">name</replaceable> <listitem> <para> This clause specifies the type of access method to define. - Only <literal>TABLE</literal> and <literal>INDEX</literal> - are supported at present. + Only <literal>TABLE</literal>, <literal>SEQUENCE</literal> and + <literal>INDEX</literal> are supported at present. </para> </listitem> </varlistentry> @@ -77,12 +77,15 @@ CREATE ACCESS METHOD <replaceable class="parameter">name</replaceable> declared to take a single argument of type <type>internal</type>, and its return type depends on the type of access method; for <literal>TABLE</literal> access methods, it must - be <type>table_am_handler</type> and for <literal>INDEX</literal> - access methods, it must be <type>index_am_handler</type>. + be <type>table_am_handler</type>; for <literal>INDEX</literal> + access methods, it must be <type>index_am_handler</type>; + for <literal>SEQUENCE</literal>, it must be + <type>sequence_am_handler</type>; 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 <xref linkend="tableam"/> and the index access method - API is described in <xref linkend="indexam"/>. + is described in <xref linkend="tableam"/>, the index access method + API is described in <xref linkend="indexam"/> and the sequence access + method is described in <xref linkend="sequenceam"/>. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/create_sequence.sgml index 1e283f13d1..52c6096e4b 100644 --- a/doc/src/sgml/ref/create_sequence.sgml +++ b/doc/src/sgml/ref/create_sequence.sgml @@ -29,6 +29,7 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceab [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ] + [ USING <replaceable class="parameter">access_method</replaceable> ] </synopsis> </refsynopsisdiv> @@ -263,6 +264,17 @@ SELECT * FROM <replaceable>name</replaceable>; </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>USING</literal> <replaceable class="parameter">access_method</replaceable></term> + <listitem> + <para> + The <literal>USING</literal> option specifies which sequence access + method will be used when generating the sequence numbers. The default + is <literal>local</literal>. + </para> + </listitem> + </varlistentry> </variablelist> </refsect1> 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 @@ +<!-- doc/src/sgml/sequenceam.sgml --> + +<chapter id="sequenceam"> + <title>Sequence Access Method Interface Definition</title> + + <indexterm> + <primary>Sequence Access Method</primary> + </indexterm> + <indexterm> + <primary>sequenceam</primary> + <secondary>Sequence Access Method</secondary> + </indexterm> + + <para> + This chapter explains the interface between the core + <productname>PostgreSQL</productname> system and <firstterm>sequence access + methods</firstterm>, 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. + </para> + + <para> + Each sequence access method is described by a row in the + <link linkend="catalog-pg-am"><structname>pg_am</structname></link> system + catalog. The <structname>pg_am</structname> entry specifies a name and a + <firstterm>handler function</firstterm> for the sequence access method. These + entries can be created and deleted using the + <xref linkend="sql-create-access-method"/> and + <xref linkend="sql-drop-access-method"/> SQL commands. + </para> + + <para> + A sequence access method handler function must be declared to accept a single + argument of type <type>internal</type> and to return the pseudo-type + <type>sequence_am_handler</type>. The argument is a dummy value that simply + 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 + <structname>SequenceAmRoutine</structname>, which contains everything that the + core code needs to know to make use of the sequence access method. The return + value needs to be of server lifetime, which is typically achieved by + defining it as a <literal>static const</literal> variable in global + scope. The <structname>SequenceAmRoutine</structname> struct, also called the + access method's <firstterm>API struct</firstterm>, 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 + <structname>SequenceAmRoutine</structname> structure (with comments inside + 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 <ulink url="https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/include/access/sequenceam.h;hb=HEAD"> + <filename>src/include/access/sequenceam.h</filename></ulink> file. + </para> + + <para> + Currently, the way a sequence access method stores data is fairly + unconstrained, and it is possible to use a predefined + <link linkend="tableam">Table Access Method</link> to store sequence + data. + </para> + + <para> + For crash safety, a sequence access method can use + <link linkend="wal"><acronym>WAL</acronym></link>, or a custom + implementation. + If <acronym>WAL</acronym> is chosen, either + <link linkend="generic-wal">Generic WAL Records</link> can be used, or a + <link linkend="custom-rmgr">Custom WAL Resource Manager</link> can be + implemented. + </para> + + <para> + Any developer of a new <literal>sequence access method</literal> can refer to + the existing <literal>local</literal> implementation present in + <filename>src/backend/access/sequence/local.c</filename> for details of + its implementation. + </para> +</chapter> -- 2.45.2 [text/x-diff] v9-0007-snowflake-Add-sequence-AM-based-on-it.patch (28.0K, ../../[email protected]/8-v9-0007-snowflake-Add-sequence-AM-based-on-it.patch) download | inline diff: From 3eb134871b381465a5fcc98e84979e182259568b Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 26 Apr 2024 15:09:00 +0900 Subject: [PATCH v9 7/7] snowflake: Add sequence AM based on it This includes documentation and a basic implementation, though this is in a very early stage. --- doc/src/sgml/contrib.sgml | 1 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/snowflake.sgml | 100 ++++ contrib/Makefile | 1 + contrib/meson.build | 1 + contrib/snowflake/.gitignore | 3 + contrib/snowflake/Makefile | 19 + contrib/snowflake/expected/snowflake.out | 73 +++ contrib/snowflake/meson.build | 34 ++ contrib/snowflake/snowflake--1.0.sql | 21 + contrib/snowflake/snowflake.c | 570 +++++++++++++++++++++++ contrib/snowflake/snowflake.control | 5 + contrib/snowflake/sql/snowflake.sql | 29 ++ 13 files changed, 858 insertions(+) create mode 100644 doc/src/sgml/snowflake.sgml create mode 100644 contrib/snowflake/.gitignore create mode 100644 contrib/snowflake/Makefile create mode 100644 contrib/snowflake/expected/snowflake.out create mode 100644 contrib/snowflake/meson.build create mode 100644 contrib/snowflake/snowflake--1.0.sql create mode 100644 contrib/snowflake/snowflake.c create mode 100644 contrib/snowflake/snowflake.control create mode 100644 contrib/snowflake/sql/snowflake.sql diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml index 44639a8dca..e7b8871e8a 100644 --- a/doc/src/sgml/contrib.sgml +++ b/doc/src/sgml/contrib.sgml @@ -166,6 +166,7 @@ CREATE EXTENSION <replaceable>extension_name</replaceable>; &seg; &sepgsql; &contrib-spi; + &snowflake; &sslinfo; &tablefunc; &tcn; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 07aa3f18c7..496ac19f25 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -156,6 +156,7 @@ <!ENTITY seg SYSTEM "seg.sgml"> <!ENTITY contrib-spi SYSTEM "contrib-spi.sgml"> <!ENTITY sepgsql SYSTEM "sepgsql.sgml"> +<!ENTITY snowflake SYSTEM "snowflake.sgml"> <!ENTITY sslinfo SYSTEM "sslinfo.sgml"> <!ENTITY tablefunc SYSTEM "tablefunc.sgml"> <!ENTITY tcn SYSTEM "tcn.sgml"> diff --git a/doc/src/sgml/snowflake.sgml b/doc/src/sgml/snowflake.sgml new file mode 100644 index 0000000000..060699e7ec --- /dev/null +++ b/doc/src/sgml/snowflake.sgml @@ -0,0 +1,100 @@ +<!-- doc/src/sgml/snowflake.sgml --> + +<sect1 id="snowflake" xreflabel="snowflake"> + <title>snowflake — sequence access method</title> + + <indexterm zone="snowflake"> + <primary>snowflake</primary> + </indexterm> + + <para> + <literal>snowflake</literal> provides a sequence access method based on + <ulink url="https://en.wikipedia.org/wiki/Snowflake_ID">Snowflake IDs</ulink>. + </para> + + <para> + A Snowflake ID (or snowflake) is a unique 64-bit identifier made of three + components: + <itemizedlist spacing="compact"> + <listitem><para>41 bits for a timestamp, epoch-adjusted in milli-seconds</para></listitem> + <listitem><para>10 bits for machine ID</para></listitem> + <listitem><para>12 bits for a sequence number</para></listitem> + </itemizedlist> + </para> + + <sect2 id="snowflake-functions"> + <title>Functions</title> + + <variablelist> + <varlistentry> + <term> + <function>snowflake_get(raw int8) returns record</function> + <indexterm> + <primary>snowflake_get</primary> + <secondary>function</secondary> + </indexterm> + </term> + + <listitem> + <para> + Returns a record made of the timestamp in milli-seconds, the machine ID + and the sequence number for a single snowflake ID. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-parameters"> + <title>Configuration Parameters</title> + + <variablelist> + <varlistentry> + <term> + <varname>snowflake.machine_id</varname> + <indexterm> + <primary><varname>snowflake.machine_id</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Machine ID assigned to the snowflake IDs used in the sequence. The + default value is <literal>1</literal>. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-examples"> + <title>Examples</title> + + <para> + This is an example of creating a snowflake sequence: + </para> + +<programlisting> +CREATE SEQUENCE snowflake_seq USING snowflake; +</programlisting> + + <para> + Similarly to the default sequence access method, snowflake sequences + can be queried as a table: + </para> + +<programlisting> + =# SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) +=# SELECT to_timestamp(time_ms / 1000), machine, counter + FROM snowflake_get(nextval('snowflake_seq')); + to_timestamp | machine | counter +------------------------+---------+--------- + 2024-04-26 14:28:26+09 | 1 | 3 +(1 row) +</programlisting> + </sect2> + +</sect1> diff --git a/contrib/Makefile b/contrib/Makefile index abd780f277..f5d1b568b2 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -42,6 +42,7 @@ SUBDIRS = \ pg_walinspect \ postgres_fdw \ seg \ + snowflake \ spi \ tablefunc \ tcn \ diff --git a/contrib/meson.build b/contrib/meson.build index 14a8906865..4f2603e7e9 100644 --- a/contrib/meson.build +++ b/contrib/meson.build @@ -57,6 +57,7 @@ subdir('pg_walinspect') subdir('postgres_fdw') subdir('seg') subdir('sepgsql') +subdir('snowflake') subdir('spi') subdir('sslinfo') # start-scripts doesn't contain build products diff --git a/contrib/snowflake/.gitignore b/contrib/snowflake/.gitignore new file mode 100644 index 0000000000..44d119cfcc --- /dev/null +++ b/contrib/snowflake/.gitignore @@ -0,0 +1,3 @@ +# Generated subdirectories +/log/ +/results/ diff --git a/contrib/snowflake/Makefile b/contrib/snowflake/Makefile new file mode 100644 index 0000000000..fa5b48d565 --- /dev/null +++ b/contrib/snowflake/Makefile @@ -0,0 +1,19 @@ +# contrib/snowflake/Makefile + +MODULES = snowflake + +EXTENSION = snowflake +DATA = snowflake--1.0.sql + +REGRESS = snowflake + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/snowflake +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/snowflake/expected/snowflake.out b/contrib/snowflake/expected/snowflake.out new file mode 100644 index 0000000000..b7e469bf73 --- /dev/null +++ b/contrib/snowflake/expected/snowflake.out @@ -0,0 +1,73 @@ +CREATE EXTENSION snowflake; +CREATE SEQUENCE snowflake_seq USING snowflake; +SET snowflake.machine_id = 2000; -- error +ERROR: 2000 is outside the valid range for parameter "snowflake.machine_id" (0 .. 1023) +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(lastval()); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 3 | t +(1 row) + +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + relpersistence +---------------- + u +(1 row) + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; + machine | counter +---------+--------- + 4 | 2 + 4 | 3 + 4 | 4 + 4 | 5 + 4 | 6 + 4 | 7 + 4 | 8 + 4 | 9 + 4 | 10 + 4 | 11 +(10 rows) + +DROP TABLE snowflake_tab; +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; diff --git a/contrib/snowflake/meson.build b/contrib/snowflake/meson.build new file mode 100644 index 0000000000..567669eea7 --- /dev/null +++ b/contrib/snowflake/meson.build @@ -0,0 +1,34 @@ +# Copyright (c) 2024, PostgreSQL Global Development Group + +snowflake_sources = files( + 'snowflake.c', +) + +if host_system == 'windows' + snowflake_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'snowflake', + '--FILEDESC', 'snowflake - sequence access method',]) +endif + +snowflake = shared_module('snowflake', + snowflake_sources, + kwargs: contrib_mod_args, +) +contrib_targets += bloom + +install_data( + 'snowflake.control', + 'snowflake--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'snowflake', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'snowflake', + ], + }, +} diff --git a/contrib/snowflake/snowflake--1.0.sql b/contrib/snowflake/snowflake--1.0.sql new file mode 100644 index 0000000000..bcb9d754f1 --- /dev/null +++ b/contrib/snowflake/snowflake--1.0.sql @@ -0,0 +1,21 @@ +/* contrib/snowflake/snowflake--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION snowflake" to load this file. \quit + +CREATE FUNCTION snowflake_sequenceam_handler(internal) + RETURNS sequence_am_handler + AS 'MODULE_PATHNAME' + LANGUAGE C; + +CREATE ACCESS METHOD snowflake + TYPE SEQUENCE HANDLER snowflake_sequenceam_handler; +COMMENT ON ACCESS METHOD snowflake IS 'snowflake sequence access method'; + +CREATE FUNCTION snowflake_get(IN raw int8, + OUT time_ms int8, + OUT machine int4, + OUT counter int4) + RETURNS record + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT diff --git a/contrib/snowflake/snowflake.c b/contrib/snowflake/snowflake.c new file mode 100644 index 0000000000..be4c4039ec --- /dev/null +++ b/contrib/snowflake/snowflake.c @@ -0,0 +1,570 @@ +/*------------------------------------------------------------------------- + * + * snowflake.c + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * contrib/snowflake/snowflake.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <sys/time.h> + +#include "access/generic_xlog.h" +#include "access/sequenceam.h" +#include "access/xact.h" +#include "catalog/storage_xlog.h" +#include "commands/tablecmds.h" +#include "fmgr.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "storage/bufmgr.h" +#include "utils/guc.h" +#include "utils/numeric.h" + +PG_MODULE_MAGIC; + +/* "special area" of a snowflake's buffer page. */ +#define SNOWFLAKE_MAGIC 0x01 + +typedef struct snowflake_magic +{ + uint32 magic; +} snowflake_magic; + +/* ----------------------------------------------------------------------- + * Snowflake ID are 64-bit based, with the following structure: + * - 41 bits for an epoch-based timestamp, in milli-seconds. + * - 10 bits for a machine ID. + * - 12 bits for a sequence counter. + * + * The timestamp can be cut to an offset. The machine ID is controlled + * by a superuser GUC. Sequence properties apply to the sequence counter, + * as the other two are environment-dependent. + * ----------------------------------------------------------------------- + */ + +/* + * Helper routines to convert a snowflake ID from/to an int64. + */ +#define SNOWFLAKE_COUNTER_MASK 0x0000000000000FFF /* 12 bits */ +#define SNOWFLAKE_COUNTER_SHIFT 0 +#define SNOWFLAKE_MACHINE_ID_MASK 0x00000000000003FF /* 10 bits */ +#define SNOWFLAKE_MACHINE_ID_SHIFT 12 /* counter */ +#define SNOWFLAKE_TIMESTAMP_MASK 0x000001FFFFFFFFFF /* 41 bits */ +#define SNOWFLAKE_TIMESTAMP_SHIFT 22 /* machine ID + counter sizes */ + +typedef struct snowflake_id +{ + uint32 count; + uint32 machine; + uint64 time_ms; +} snowflake_id; + +#define snowflake_id_to_int64(id, raw) { \ + raw = (((id).count) & SNOWFLAKE_COUNTER_MASK) << SNOWFLAKE_COUNTER_SHIFT | \ + (((id).machine) & SNOWFLAKE_MACHINE_ID_MASK) << SNOWFLAKE_MACHINE_ID_SHIFT | \ + (((id).time_ms) & SNOWFLAKE_TIMESTAMP_MASK) << SNOWFLAKE_TIMESTAMP_SHIFT; \ +} + +#define int64_to_snowflake_id(raw, id) { \ + (id).count = ((raw) >> SNOWFLAKE_COUNTER_SHIFT) & SNOWFLAKE_COUNTER_MASK; \ + (id).machine = ((raw) >> SNOWFLAKE_MACHINE_ID_SHIFT) & SNOWFLAKE_MACHINE_ID_MASK; \ + (id).time_ms = ((raw) >> SNOWFLAKE_TIMESTAMP_SHIFT) & SNOWFLAKE_TIMESTAMP_MASK; \ +} + +/* + * Format of tuples stored in heap table associated to snowflake sequence. + */ +typedef struct FormData_snowflake_data +{ + /* enough to cover 12 bits of the internal counter */ + int16 count; + bool is_called; +} FormData_snowflake_data; + +typedef FormData_snowflake_data *Form_snowflake_data; + +/* + * Columns of a snowflake sequence relation. + */ +#define SNOWFLAKE_COL_COUNT 1 +#define SNOWFLAKE_COL_CALLED 2 + +#define SNOWFLAKE_COLS 2 + +/* GUC parameter */ +static int snowflake_machine_id = 1; + +PG_FUNCTION_INFO_V1(snowflake_sequenceam_handler); + +/* ----------------------------------------------------------------------- + * Interfaces for relation manipulation. + * ----------------------------------------------------------------------- + */ + +/* + * Initialize snowflake relation's fork with some data. + */ +static void +fill_snowflake_fork(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + snowflake_magic *sm; + OffsetNumber offnum; + GenericXLogState *state = NULL; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(snowflake_magic)); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + sm->magic = SNOWFLAKE_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= HEAP_XMAX_INVALID; + ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber); + + /* + * Initialize before entering in the critical section, as this does + * allocations. + */ + if (forkNum == INIT_FORKNUM) + state = GenericXLogStart(rel); + + START_CRIT_SECTION(); + + MarkBufferDirty(buf); + + offnum = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* + * Init forks have to be logged. These go through generic WAL records + * for simplicity's sake to save from the need of a custom RMGR. + */ + if (forkNum == INIT_FORKNUM) + { + (void) GenericXLogRegisterBuffer(state, buf, GENERIC_XLOG_FULL_IMAGE); + GenericXLogFinish(state); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * Initialize snowflake relation. + * + * This needs to handle both the initial and main forks. + */ +static void +fill_snowflake(Relation rel, HeapTuple tuple) +{ + SMgrRelation srel; + + Assert(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED); + + fill_snowflake_fork(rel, tuple, MAIN_FORKNUM); + + /* init fork */ + srel = smgropen(rel->rd_locator, INVALID_PROC_NUMBER); + smgrcreate(srel, INIT_FORKNUM, false); + log_smgrcreate(&rel->rd_locator, INIT_FORKNUM); + fill_snowflake_fork(rel, tuple, INIT_FORKNUM); + FlushRelationBuffers(rel); + smgrclose(srel); +} + +/* + * Read the current state of a snowflake sequence + * + * Given an opened sequence relation, lock the page buffer and find the tuple. + * + * *buf receives the reference to the pinned-and-ex-locked buffer. + * *seqdatatuple receives the reference to the sequence tuple proper. + * + * Returns value points to the data payload of the tuple. + */ +static Form_snowflake_data +read_snowflake(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + snowflake_magic *sm; + Form_snowflake_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SNOWFLAKE_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_snowflake_data) GETSTRUCT(seqdatatuple); + + return seq; +} + + +/* ------------------------------------------------------------------------ + * Callbacks for the snowflake 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 * +snowflake_sequenceam_get_table_am(void) +{ + return "heap"; +} + +/* + * snowflake_sequenceam_init + * + * Initialize relation of a snowflake sequence. This stores the sequence + * counter in an unlogged relation as timestamps ensure value unicity. + */ +static void +snowflake_sequenceam_init(Relation rel, int64 last_value, bool is_called) +{ + Datum values[SNOWFLAKE_COLS]; + bool nulls[SNOWFLAKE_COLS]; + int16 counter; + TupleDesc tupdesc; + HeapTuple tuple; + List *elts = NIL; + ListCell *lc; + ColumnDef *coldef = NULL; + AlterTableCmd *atcmd; + List *atcmds = NIL; + + /* Adjust last_value, depending on the defaults given */ + counter = ((int16) last_value) & SNOWFLAKE_COUNTER_MASK; + + /* + * Create unlogged relation with its attributes. + */ + coldef = makeColumnDef("count", INT2OID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + + foreach(lc, elts) + { + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + /* + * Switch the relation to be unlogged. This forces a rewrite, but + * the relation is empty so that's OK. + */ + RelationSetNewRelfilenumber(rel, RELPERSISTENCE_UNLOGGED); + + /* And insert its first tuple */ + values[0] = Int16GetDatum(counter); + nulls[0] = false; + values[1] = BoolGetDatum(is_called); + nulls[1] = false; + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, values, nulls); + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_nextval + * + * Return the next value for a snowflake sequence. + */ +static int64 +snowflake_sequenceam_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + Buffer buf; + Form_snowflake_data seq; + HeapTupleData seqdatatuple; + int64 result = 0; + snowflake_id id = {0}; + struct timeval tp; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* + * The logic here is quite simple, increment the counter until its + * threshold is reached and get back to the start. If the threshold + * is reached, wait 1ms to ensure a unique timestamp. There is no + * need to do a retry as the buffer is already locked. + */ + id.count = seq->count; + id.count++; + + if (id.count > (PG_INT16_MAX & SNOWFLAKE_COUNTER_MASK)) + { + /* + * Threshold reached, so wait a bit for force clock to a new + * timestamp. + */ + id.count = 1; + pg_usleep(1000L); /* 1ms */ + } + + /* Compute timestamp, with buffer locked */ + gettimeofday(&tp, NULL); + id.time_ms = (uint64) tp.tv_sec * 1000 + + tp.tv_usec / 1000; + + /* Machine ID */ + id.machine = snowflake_machine_id; + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->count = id.count; + seq->is_called = true; + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + /* Store the last value computed for lastval() */ + snowflake_id_to_int64(id, result); + *last = result; + return result; +} + +/* + * snowflake_sequenceam_setval + * + * Set the sequence value, manipulating only the sequence counter. + */ +static void +snowflake_sequenceam_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* Change the in-buffer tuple */ + START_CRIT_SECTION(); + seq->count = (next & SNOWFLAKE_COUNTER_MASK); + seq->is_called = iscalled; + MarkBufferDirty(buf); + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_get_state + * + * Return the last sequence counter value. + */ +static void +snowflake_sequenceam_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + seq = read_snowflake(rel, &buf, &seqdatatuple); + *last_value = seq->count; + *is_called = seq->is_called; + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_reset + * + * Reset the sequence, coming down to resetting its counter. + */ +static void +snowflake_sequenceam_reset(Relation rel, int64 startv, bool is_called, + bool reset_state) +{ + HeapTupleData seqdatatuple; + HeapTuple tuple; + Form_snowflake_data seq; + Buffer buf; + + /* lock buffer page and read tuple */ + (void) read_snowflake(rel, &buf, &seqdatatuple); + + /* copy the existing tuple */ + tuple = 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 = (Form_snowflake_data) GETSTRUCT(tuple); + seq->count = (startv & SNOWFLAKE_COUNTER_MASK); + seq->is_called = is_called; + + /* create new storage */ + RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence); + + /* insert the modified tuple into the page */ + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_change_persistence + * + * There is nothing to do here, the underneath relation has to remain + * unlogged and is set as such when creating the sequence. + */ +static void +snowflake_sequenceam_change_persistence(Relation rel, char newrelpersistence) +{ + /* Nothing to do here */ +} + +/* ------------------------------------------------------------------------ + * Definition of the snowflake sequence access method. + * ------------------------------------------------------------------------ + */ + +static const SequenceAmRoutine snowflake_sequenceam_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = snowflake_sequenceam_get_table_am, + .init = snowflake_sequenceam_init, + .nextval = snowflake_sequenceam_nextval, + .setval = snowflake_sequenceam_setval, + .get_state = snowflake_sequenceam_get_state, + .reset = snowflake_sequenceam_reset, + .change_persistence = snowflake_sequenceam_change_persistence +}; + +Datum +snowflake_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&snowflake_sequenceam_methods); +} + +/* Utility functions */ + +/* + * snowflake_get + * + * Return a tuple worth of snowflake ID data, in a readable shape. + */ +PG_FUNCTION_INFO_V1(snowflake_get); +Datum +snowflake_get(PG_FUNCTION_ARGS) +{ +#define SNOWFLAKE_GET_COLS 3 + int64 raw = PG_GETARG_INT64(0); + Datum *values; + bool *nulls; + TupleDesc tupdesc; + snowflake_id id; + + /* determine result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + int64_to_snowflake_id(raw, id); + + nulls = palloc0(sizeof(bool) * tupdesc->natts); + values = palloc0(sizeof(Datum) * tupdesc->natts); + + values[0] = Int64GetDatum(id.time_ms); + values[1] = Int32GetDatum(id.machine); + values[2] = Int32GetDatum(id.count); + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Entry point when loading extension. + */ +void +_PG_init(void) +{ + DefineCustomIntVariable("snowflake.machine_id", + "Machine ID to use with snowflake sequence.", + "Default value is 1.", + &snowflake_machine_id, + 1, 0, 1023, /* 10 bits as max */ + PGC_SUSET, + 0, NULL, NULL, NULL); +} diff --git a/contrib/snowflake/snowflake.control b/contrib/snowflake/snowflake.control new file mode 100644 index 0000000000..7b8c6089c2 --- /dev/null +++ b/contrib/snowflake/snowflake.control @@ -0,0 +1,5 @@ +# snowflake extension +comment = 'snowflake - sequence access method' +default_version = '1.0' +module_pathname = '$libdir/snowflake' +relocatable = true diff --git a/contrib/snowflake/sql/snowflake.sql b/contrib/snowflake/sql/snowflake.sql new file mode 100644 index 0000000000..395d166ba4 --- /dev/null +++ b/contrib/snowflake/sql/snowflake.sql @@ -0,0 +1,29 @@ +CREATE EXTENSION snowflake; + +CREATE SEQUENCE snowflake_seq USING snowflake; + +SET snowflake.machine_id = 2000; -- error +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(lastval()); +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; +DROP TABLE snowflake_tab; + +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; -- 2.45.2 [application/pgp-signature] signature.asc (833B, ../../[email protected]/9-signature.asc) download ^ permalink raw reply [nested|flat] 14+ messages in thread
* Re: Sequence Access Methods, round two @ 2024-12-03 09:24 Kirill Reshke <[email protected]> parent: Michael Paquier <[email protected]> 0 siblings, 1 reply; 14+ messages in thread From: Kirill Reshke @ 2024-12-03 09:24 UTC (permalink / raw) To: Michael Paquier <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Peter Smith <[email protected]>; Postgres hackers <[email protected]> On Wed, 2 Oct 2024 at 10:29, Michael Paquier <[email protected]> wrote: > > Rebased as v29 due to an OID conflict. Hi! Looks like this needs another rebase -- Best regards, Kirill Reshke ^ permalink raw reply [nested|flat] 14+ messages in thread
* Re: Sequence Access Methods, round two @ 2024-12-03 23:36 Michael Paquier <[email protected]> parent: Kirill Reshke <[email protected]> 0 siblings, 1 reply; 14+ messages in thread From: Michael Paquier @ 2024-12-03 23:36 UTC (permalink / raw) To: Kirill Reshke <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Peter Smith <[email protected]>; Postgres hackers <[email protected]> On Tue, Dec 03, 2024 at 02:24:41PM +0500, Kirill Reshke wrote: > Looks like this needs another rebase Indeed. There were a couple of blips in the backend and the dump parts of the patch set. Rebased as v10 attached. -- Michael Attachments: [text/x-diff] v10-0001-Remove-FormData_pg_sequence_data-from-init_param.patch (9.2K, ../../[email protected]/2-v10-0001-Remove-FormData_pg_sequence_data-from-init_param.patch) download | inline diff: From 712810957959e4a6a4211deaa0619cfe84ddf9f8 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:00:45 +0900 Subject: [PATCH v10 1/7] 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/sequence.c index 0188e8bbd5..ed2cd2b1fc 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 rel, 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); 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 = makeNode(CreateStmt); @@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) /* 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); /* @@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) { case SEQ_COL_LASTVAL: coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(seqdataform.last_value); + value[i - 1] = Int64GetDatumFast(last_value); break; case SEQ_COL_LOG: coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); @@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) ObjectAddress address; Relation rel; HeapTuple seqtuple; + bool reset_state = false; + bool is_called; + int64 last_value; HeapTuple newdatatuple; /* 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 = heap_copytuple(&datatuple); newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); + last_value = newdataform->last_value; + is_called = newdataform->is_called; UnlockReleaseBuffer(buf); /* 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); /* 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 = last_value; + newdataform->is_called = is_called; + if (reset_state) + newdataform->log_cnt = 0; fill_seq_with_data(seqrel, newdatatuple); } @@ -1236,17 +1249,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). Also + * 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 overridden. * * Note: we force a sequence rewrite whenever we change parameters that affect - * 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. Currently, * 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 @@ -1257,7 +1272,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) { @@ -1363,11 +1380,11 @@ init_params(ParseState *pstate, List *options, bool for_identity, } /* - * 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 = 0; + *reset_state = true; /* AS type */ if (as_type != NULL) @@ -1416,7 +1433,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("INCREMENT must not be zero"))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1428,7 +1445,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, { seqform->seqcycle = boolVal(is_cycled->arg); Assert(BoolIsValid(seqform->seqcycle)); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1439,7 +1456,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (max_value != NULL && max_value->arg) { seqform->seqmax = defGetInt64(max_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || max_value != NULL || reset_max_value) { @@ -1455,7 +1472,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmax = -1; /* descending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate maximum value. No need to check INT8 as seqmax is an int64 */ @@ -1471,7 +1488,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (min_value != NULL && min_value->arg) { seqform->seqmin = defGetInt64(min_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || min_value != NULL || reset_min_value) { @@ -1487,7 +1504,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmin = 1; /* ascending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate minimum value. No need to check INT8 as seqmin is an int64 */ @@ -1538,30 +1555,30 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (restart_value != NULL) { if (restart_value->arg != NULL) - seqdataform->last_value = defGetInt64(restart_value); + *last_value = defGetInt64(restart_value); else - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; - seqdataform->log_cnt = 0; + *last_value = seqform->seqstart; + *is_called = false; + *reset_state = true; } else if (isInit) { - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; + *last_value = seqform->seqstart; + *is_called = false; } /* 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))); /* CACHE */ @@ -1573,7 +1590,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("CACHE (%lld) must be greater than zero", (long long) seqform->seqcache))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { -- 2.45.2 [text/x-diff] v10-0002-Integrate-addition-of-attributes-for-sequences-w.patch (11.1K, ../../[email protected]/3-v10-0002-Integrate-addition-of-attributes-for-sequences-w.patch) download | inline diff: From c171568fa26c03fe40541c5c572811bb33c432f7 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 19 Jan 2024 15:00:14 +0900 Subject: [PATCH v10 2/7] 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 0f9462493e..26f0b45c7e 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2358,6 +2358,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/sequence.c index ed2cd2b1fc..158ea79c66 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 = NIL; + List *atcmds = 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 = NIL; for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) { ColumnDef *coldef = NULL; @@ -198,7 +200,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) coldef->is_not_null = true; null[i - 1] = false; - stmt->tableElts = lappend(stmt->tableElts, coldef); + elts = lappend(elts, coldef); } stmt->relation = seq->sequence; @@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; + /* + * Initial relation has no attributes, these are added later. + */ + stmt->tableElts = NIL; address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + tupDesc = RelationGetDescr(rel); /* now initialize the sequence's data */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 6ccae4cb4a..390b029394 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4569,6 +4569,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 */ @@ -4864,6 +4865,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* Recursion occurs during execution phase */ pass = 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 = 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, @@ -5294,6 +5302,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 = ATExecAddColumn(wqueue, tab, rel, &cmd, cmd->recurse, false, @@ -6446,6 +6455,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 f28bf37105..ac6536e59e 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1667,7 +1667,11 @@ ProcessUtilitySlow(ParseState *pstate, break; case T_CreateSeqStmt: + EventTriggerAlterTableStart(parsetree); address = DefineSequence(pstate, (CreateSeqStmt *) parsetree); + /* stashed internally */ + commandCollected = true; + EventTriggerAlterTableEnd(); break; case T_AlterSeqStmt: diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/test/modules/test_ddl_deparse/expected/alter_table.out index 50d0354a34..ed31059ef5 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 of sequence parent_b_seq NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type ADD COLUMN (and recurse) desc column b of table parent NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint parent_b_not_null on table parent @@ -70,7 +73,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 parent_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 of 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 table parent diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out 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 of 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 14915f661a..527c67995a 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 of 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 of 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 of sequence datatype_table_is_small_seq NOTICE: DDL test: type simple, tag CREATE TABLE NOTICE: DDL test: type simple, tag CREATE INDEX NOTICE: DDL test: type simple, tag CREATE INDEX diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c index 98d237ac68..8cc073bdc1 100644 --- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c +++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c @@ -113,6 +113,9 @@ get_altertable_subcmdinfo(PG_FUNCTION_ARGS) case AT_AddColumn: strtype = "ADD COLUMN"; break; + case AT_AddColumnToSequence: + strtype = "ADD COLUMN TO SEQUENCE"; + break; case AT_AddColumnToView: strtype = "ADD COLUMN TO VIEW"; break; -- 2.45.2 [text/x-diff] v10-0003-Refactor-code-for-in-core-local-sequences.patch (54.5K, ../../[email protected]/4-v10-0003-Refactor-code-for-in-core-local-sequences.patch) download | inline diff: From eff690308995c3d128daa70bde340e8bd0c11a2a Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Thu, 25 Apr 2024 16:49:47 +0900 Subject: [PATCH v10 3/7] Refactor code for in-core "local" sequences This commit restructures the code of in-core sequences into a new set of files: - seqdesc.c is renamed to seqlocaldesc.c. - seqlocalxlog.c to the code in the WAL replay logic. - seqlocalam.c to store a set of routines called from sequence.c, finishing the separation between the main sequence logic and the in-core sequences. - seqlocalam.h to store the AM-specific structures and routine related to the in-core sequences. WAL records are renamed to "SequenceLocal" with structures, variables and file structures mapping to that. --- src/include/access/rmgrlist.h | 2 +- src/include/access/seqlocalam.h | 58 ++ src/backend/access/rmgrdesc/Makefile | 2 +- src/backend/access/rmgrdesc/meson.build | 2 +- .../rmgrdesc/{seqdesc.c => seqlocaldesc.c} | 20 +- src/backend/access/sequence/Makefile | 2 +- src/backend/access/sequence/meson.build | 2 + src/backend/access/sequence/seqlocalam.c | 634 ++++++++++++++++++ src/backend/access/sequence/seqlocalxlog.c | 82 +++ src/backend/access/transam/rmgr.c | 1 + src/backend/commands/sequence.c | 634 +----------------- src/bin/pg_waldump/.gitignore | 2 +- src/bin/pg_waldump/rmgrdesc.c | 1 + src/bin/pg_waldump/t/001_basic.pl | 2 +- 14 files changed, 822 insertions(+), 622 deletions(-) create mode 100644 src/include/access/seqlocalam.h rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%) create mode 100644 src/backend/access/sequence/seqlocalam.c create mode 100644 src/backend/access/sequence/seqlocalxlog.c diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h index 78e6b908c6..fa94beb6ed 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, btree_identify, btree_xlog PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL, hash_mask, NULL) PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, 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, NULL, seq_mask, NULL) +PG_RMGR(RM_SEQ_LOCAL_ID, "SequenceLocal", seq_local_redo, seq_local_desc, seq_local_identify, NULL, NULL, seq_local_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, NULL, brin_mask, NULL) PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL, NULL, NULL) diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h new file mode 100644 index 0000000000..225fb9a2cb --- /dev/null +++ b/src/include/access/seqlocalam.h @@ -0,0 +1,58 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.h + * Local sequence access method. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/access/seqlocalam.h + * + *------------------------------------------------------------------------- + */ +#ifndef SEQLOCALAM_H +#define SEQLOCALAM_H + +#include "access/xlogreader.h" +#include "storage/relfilelocator.h" +#include "utils/rel.h" + +/* XLOG stuff */ +#define XLOG_SEQ_LOCAL_LOG 0x00 + +typedef struct xl_seq_local_rec +{ + RelFileLocator locator; + /* SEQUENCE TUPLE DATA FOLLOWS AT THE END */ +} xl_seq_local_rec; + +/* + * The "special area" of a local sequence's buffer page looks like this. + */ +#define SEQ_LOCAL_MAGIC 0x1717 + +typedef struct seq_local_magic +{ + uint32 magic; +} seq_local_magic; + +extern void seq_local_redo(XLogReaderState *record); +extern void seq_local_desc(StringInfo buf, XLogReaderState *record); +extern const char *seq_local_identify(uint8 info); +extern void seq_local_mask(char *page, BlockNumber blkno); + +/* access routines */ +extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last); +extern const char *seq_local_get_table_am(void); +extern void seq_local_init(Relation rel, int64 last_value, bool is_called); +extern void seq_local_setval(Relation rel, int64 next, bool iscalled); +extern void seq_local_reset(Relation rel, int64 startv, bool is_called, + bool reset_state); +extern void seq_local_get_state(Relation rel, int64 *last_value, + bool *is_called); +extern void seq_local_change_persistence(Relation rel, + char newrelpersistence); + +#endif /* SEQLOCALAM_H */ diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile index cd95eec37f..e5900ed77a 100644 --- a/src/backend/access/rmgrdesc/Makefile +++ b/src/backend/access/rmgrdesc/Makefile @@ -24,7 +24,7 @@ OBJS = \ relmapdesc.o \ replorigindesc.o \ rmgrdesc_utils.o \ - seqdesc.o \ + seqlocaldesc.o \ smgrdesc.o \ spgdesc.o \ standbydesc.o \ diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build index e8b7a65fc7..e390d385bf 100644 --- a/src/backend/access/rmgrdesc/meson.build +++ b/src/backend/access/rmgrdesc/meson.build @@ -17,7 +17,7 @@ rmgr_desc_sources = files( 'relmapdesc.c', 'replorigindesc.c', 'rmgrdesc_utils.c', - 'seqdesc.c', + 'seqlocaldesc.c', 'smgrdesc.c', 'spgdesc.c', 'standbydesc.c', diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/seqlocaldesc.c similarity index 64% rename from src/backend/access/rmgrdesc/seqdesc.c rename to src/backend/access/rmgrdesc/seqlocaldesc.c index cf0e02ded5..409c67335e 100644 --- a/src/backend/access/rmgrdesc/seqdesc.c +++ b/src/backend/access/rmgrdesc/seqlocaldesc.c @@ -1,44 +1,44 @@ /*------------------------------------------------------------------------- * - * seqdesc.c - * rmgr descriptor routines for commands/sequence.c + * seqlocaldesc.c + * rmgr descriptor routines for sequence/seqlocal.c * * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * src/backend/access/rmgrdesc/seqdesc.c + * src/backend/access/rmgrdesc/seqlocaldesc.c * *------------------------------------------------------------------------- */ #include "postgres.h" -#include "commands/sequence.h" +#include "access/seqlocalam.h" void -seq_desc(StringInfo buf, XLogReaderState *record) +seq_local_desc(StringInfo buf, XLogReaderState *record) { char *rec = XLogRecGetData(record); uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - xl_seq_rec *xlrec = (xl_seq_rec *) rec; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) rec; - if (info == XLOG_SEQ_LOG) + if (info == XLOG_SEQ_LOCAL_LOG) appendStringInfo(buf, "rel %u/%u/%u", xlrec->locator.spcOid, xlrec->locator.dbOid, xlrec->locator.relNumber); } const char * -seq_identify(uint8 info) +seq_local_identify(uint8 info) { const char *id = NULL; switch (info & ~XLR_INFO_MASK) { - case XLOG_SEQ_LOG: - id = "LOG"; + case XLOG_SEQ_LOCAL_LOG: + id = "SEQ_LOCAL_LOG"; break; } diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile index 9f9d31f542..a15ceec1c0 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index 1843aed38d..4d24f900b6 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -1,5 +1,7 @@ # Copyright (c) 2022-2024, PostgreSQL Global Development Group backend_sources += files( + 'seqlocalam.c', + 'seqlocalxlog.c', 'sequence.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c new file mode 100644 index 0000000000..b0a0d72966 --- /dev/null +++ b/src/backend/access/sequence/seqlocalam.c @@ -0,0 +1,634 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.c + * Local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalam.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/multixact.h" +#include "access/seqlocalam.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 SEQ_LOCAL_LOG_VALS 32 + +/* Format of tuples stored in heap table associated to local sequences */ +typedef struct FormData_pg_seq_local_data +{ + int64 last_value; + int64 log_cnt; + bool is_called; +} FormData_pg_seq_local_data; + +typedef FormData_pg_seq_local_data *Form_pg_seq_local_data; + +/* + * Columns of a local sequence relation + */ +#define SEQ_LOCAL_COL_LASTVAL 1 +#define SEQ_LOCAL_COL_LOG 2 +#define SEQ_LOCAL_COL_CALLED 3 + +#define SEQ_LOCAL_COL_FIRSTCOL SEQ_LOCAL_COL_LASTVAL +#define SEQ_LOCAL_COL_LASTCOL SEQ_LOCAL_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_LOCAL_LOG_VALS 32 + +static Form_pg_seq_local_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 tuple + * + * *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_seq_local_data +read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + seq_local_magic *sm; + Form_pg_seq_local_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SEQ_LOCAL_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_pg_seq_local_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 == RELPERSISTENCE_UNLOGGED) + { + SMgrRelation srel; + + srel = 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 content + */ +static void +fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + seq_local_magic *sm; + OffsetNumber offnum; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + sm->magic = SEQ_LOCAL_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) tuple->t_data, tuple->t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_nextval() + * + * Allocate a new value for a local sequence, based on the sequence + * configuration. + */ +int64 +seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + int64 result; + int64 fetch; + int64 next; + int64 rescnt = 0; + int64 log; + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + Page page; + bool logit = false; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + page = BufferGetPage(buf); + + *last = next = result = seq->last_value; + fetch = cache; + log = 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_LOCAL_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 = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = true; + } + else + { + XLogRecPtr redoptr = GetRedoRecPtr(); + + if (PageGetLSN(page) <= redoptr) + { + /* last update of seq was before checkpoint */ + fetch = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = 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 >= 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 = minv; + } + else + next += incby; + } + else + { + /* descending sequence */ + if ((minv < 0 && next < minv - incby) || + (minv >= 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 = maxv; + } + else + next += incby; + } + fetch--; + if (rescnt < cache) + { + log--; + rescnt++; + *last = next; + if (rescnt == 1) /* if it's first result - */ + result = next; /* it's what to return */ + } + } + + log -= fetch; /* adjust for any unfetched numbers */ + Assert(log >= 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_seq_local_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 = next; + seq->is_called = true; + seq->log_cnt = 0; + + xlrec.locator = rel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + /* Now update sequence tuple to the intended final state */ + seq->last_value = *last; /* last fetched number */ + seq->is_called = true; + seq->log_cnt = log; /* how much is logged */ + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + return result; +} + +/* + * seq_local_get_table_am() + * + * Return the table access method used by this sequence. + */ +const char * +seq_local_get_table_am(void) +{ + return "heap"; +} + +/* + * seq_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 + * the information guessed from pg_sequences. This is the first tuple + * inserted after the relation has been created, filling in its heap + * table. + */ +void +seq_local_init(Relation rel, int64 last_value, bool is_called) +{ + Datum value[SEQ_LOCAL_COL_LASTCOL]; + bool null[SEQ_LOCAL_COL_LASTCOL]; + List *elts = NIL; + List *atcmds = NIL; + ListCell *lc; + TupleDesc tupdesc; + HeapTuple tuple; + + /* + * Create relation (and fill value[] and null[] for the initial tuple). + */ + for (int i = SEQ_LOCAL_COL_FIRSTCOL; i <= SEQ_LOCAL_COL_LASTCOL; i++) + { + ColumnDef *coldef = NULL; + + switch (i) + { + case SEQ_LOCAL_COL_LASTVAL: + coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatumFast(last_value); + break; + case SEQ_LOCAL_COL_LOG: + coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatum(0); + break; + case SEQ_LOCAL_COL_CALLED: + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + value[i - 1] = BoolGetDatum(is_called); + break; + } + + coldef->is_not_null = true; + null[i - 1] = false; + elts = lappend(elts, coldef); + } + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, value, null); + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_setval() + * + * Callback for setval(). + */ +void +seq_local_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->last_value = next; /* last fetched number */ + seq->is_called = iscalled; + seq->log_cnt = 0; + + MarkBufferDirty(buf); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel)) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + Page page = BufferGetPage(buf); + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_reset() + * + * Perform a hard reset on the local sequence, rewriting its heap data + * entirely. + */ +void +seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) +{ + Form_pg_seq_local_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 = 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 = (Form_pg_seq_local_data) GETSTRUCT(tuple); + seq->last_value = startv; + seq->is_called = is_called; + if (reset_state) + seq->log_cnt = 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 == InvalidTransactionId); + Assert(rel->rd_rel->relminmxid == InvalidMultiXactId); + + /* + * Insert the modified tuple into the new storage file. + */ + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_get_state() + * + * Retrieve the state of a local sequence. + */ +void +seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + *last_value = seq->last_value; + *is_called = seq->is_called; + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_change_persistence() + * + * Persistence change for the local sequence Relation. + */ +void +seq_local_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/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c new file mode 100644 index 0000000000..db0ad969db --- /dev/null +++ b/src/backend/access/sequence/seqlocalxlog.c @@ -0,0 +1,82 @@ +/*------------------------------------------------------------------------- + * + * seqlocalxlog.c + * WAL replay logic for local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalxlog.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/bufmask.h" +#include "access/seqlocalam.h" +#include "access/xlogutils.h" +#include "storage/block.h" + +/* + * Mask a Sequence page before performing consistency checks on it. + */ +void +seq_local_mask(char *page, BlockNumber blkno) +{ + mask_page_lsn_and_checksum(page); + + mask_unused_space(page); +} + +void +seq_local_redo(XLogReaderState *record) +{ + XLogRecPtr lsn = record->EndRecPtr; + uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; + Buffer buffer; + Page page; + Page localpage; + char *item; + Size itemsz; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record); + seq_local_magic *sm; + + if (info != XLOG_SEQ_LOCAL_LOG) + elog(PANIC, "seq_redo: unknown op code %u", info); + + buffer = XLogInitBufferForRedo(record, 0); + page = (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 = (Page) palloc(BufferGetPageSize(buffer)); + + PageInit(localpage, BufferGetPageSize(buffer), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(localpage); + sm->magic = SEQ_LOCAL_MAGIC; + + item = (char *) xlrec + sizeof(xl_seq_local_rec); + itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec); + + if (PageAddItem(localpage, (Item) item, itemsz, + FirstOffsetNumber, false, false) == InvalidOffsetNumber) + elog(PANIC, "seq_local_redo: failed to add item to page"); + + PageSetLSN(localpage, lsn); + + memcpy(page, localpage, BufferGetPageSize(buffer)); + MarkBufferDirty(buffer); + UnlockReleaseBuffer(buffer); + + pfree(localpage); +} diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c index 1b7499726e..cc92268937 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/seqlocalam.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/sequence.c index 158ea79c66..1cad06da66 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,6 +16,7 @@ #include "access/bufmask.h" #include "access/htup_details.h" +#include "access/seqlocalam.h" #include "access/multixact.h" #include "access/relation.h" #include "access/sequence.h" @@ -50,23 +51,6 @@ #include "utils/varlena.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 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 current * session. This is needed to hold onto nextval/currval state. (We can't @@ -96,13 +80,9 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */ */ static SeqTableData *last_used_seq = NULL; -static void fill_seq_with_data(Relation rel, HeapTuple tuple); -static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber 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_identity, 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 = NIL; - List *atcmds = NIL; - ListCell *lc; Datum pgs_values[Natts_pg_sequence]; bool pgs_nulls[Natts_pg_sequence]; - int i; /* * 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); - /* - * Create relation (and fill value[] and null[] for the tuple) - */ - for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) - { - ColumnDef *coldef = NULL; - - switch (i) - { - case SEQ_COL_LASTVAL: - coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(last_value); - break; - case SEQ_COL_LOG: - coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatum((int64) 0); - break; - case SEQ_COL_CALLED: - coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); - value[i - 1] = BoolGetDatum(false); - break; - } - - coldef->is_not_null = true; - null[i - 1] = false; - - elts = lappend(elts, coldef); - } - stmt->relation = seq->sequence; stmt->inhRelations = NIL; stmt->constraints = NIL; @@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) */ stmt->tableElts = NIL; + /* + * Initial relation has no attributes, these can be added later via the + * "init" AM callback. + */ + stmt->tableElts = NIL; + address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); - /* Add all the attributes to the sequence */ - foreach(lc, elts) - { - AlterTableCmd *atcmd; - - atcmd = makeNode(AlterTableCmd); - atcmd->subtype = AT_AddColumnToSequence; - atcmd->def = (Node *) lfirst(lc); - atcmds = lappend(atcmds, atcmd); - } - - /* - * No recursion needed. Note that EventTriggerAlterTableStart() should - * have been called. - */ - AlterTableInternal(RelationGetRelid(rel), atcmds, false); - CommandCounterIncrement(); - - tupDesc = RelationGetDescr(rel); - - /* now initialize the sequence's data */ - tuple = heap_form_tuple(tupDesc, value, null); - fill_seq_with_data(rel, tuple); + /* now initialize the sequence table structure and its data */ + seq_local_init(rel, last_value, is_called); /* 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); pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid)); if (!HeapTupleIsValid(pgstuple)) @@ -315,40 +240,8 @@ ResetSequence(Oid seq_relid) startv = pgsform->seqstart; ReleaseSysCache(pgstuple); - /* - * Copy the existing sequence tuple. - */ - tuple = 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 = (Form_pg_sequence_data) GETSTRUCT(tuple); - seq->last_value = startv; - seq->is_called = false; - seq->log_cnt = 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 == InvalidTransactionId); - Assert(seq_rel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - fill_seq_with_data(seq_rel, tuple); + /* Sequence state is forcibly reset here. */ + seq_local_reset(seq_rel, startv, false, true); /* 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); } -/* - * 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 == RELPERSISTENCE_UNLOGGED) - { - SMgrRelation srel; - - srel = 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 content - */ -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 = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, - EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); - Assert(BufferGetBlockNumber(buf) == 0); - - page = BufferGetPage(buf); - - PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(page); - sm->magic = SEQ_MAGIC; - - /* Now insert sequence tuple */ - - /* - * Since VACUUM does not process sequences, we have to force the tuple to - * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, - InvalidOffsetNumber, false, false); - if (offnum != FirstOffsetNumber) - elog(ERROR, "failed to add sequence tuple to page"); - - /* XLOG stuff */ - if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = rel->rd_locator; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) tuple->t_data, tuple->t_len); - - recptr = 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 = false; bool is_called; int64 last_value; - HeapTuple newdatatuple; /* Open and lock sequence, and check for ownership along the way. */ relid = RangeVarGetRelidExtended(stmt->sequence, @@ -507,16 +296,8 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) seqform = (Form_pg_sequence) GETSTRUCT(seqtuple); - /* 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 = heap_copytuple(&datatuple); - newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); - last_value = newdataform->last_value; - is_called = newdataform->is_called; - - UnlockReleaseBuffer(buf); + /* Read sequence data */ + seq_local_get_state(seqrel, &last_value, &is_called); /* 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(); - /* - * 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 == InvalidTransactionId); - Assert(seqrel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - newdataform->last_value = last_value; - newdataform->is_called = is_called; - if (reset_state) - newdataform->log_cnt = 0; - fill_seq_with_data(seqrel, newdatatuple); + seq_local_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) { SeqTable elm; Relation seqrel; - Buffer buf; - HeapTupleData seqdatatuple; /* * ALTER SEQUENCE acquires this lock earlier. If we're processing an @@ -596,10 +353,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - (void) read_seq_tuple(seqrel, &buf, &seqdatatuple); - RelationSetNewRelfilenumber(seqrel, newrelpersistence); - fill_seq_with_data(seqrel, &seqdatatuple); - UnlockReleaseBuffer(buf); + seq_local_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -662,24 +416,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 = 0; + int64 result; bool cycle; - bool logit = false; /* open and lock sequence */ init_sequence(relid, &elm, &seqrel); @@ -724,105 +469,9 @@ nextval_internal(Oid relid, bool check_permissions) cycle = pgsform->seqcycle; ReleaseSysCache(pgstuple); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - page = BufferGetPage(buf); - - last = next = result = seq->last_value; - fetch = cache; - log = 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 = log = fetch + SEQ_LOG_VALS; - logit = true; - } - else - { - XLogRecPtr redoptr = GetRedoRecPtr(); - - if (PageGetLSN(page) <= redoptr) - { - /* last update of seq was before checkpoint */ - fetch = log = fetch + SEQ_LOG_VALS; - logit = 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 >= 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 = minv; - } - else - next += incby; - } - else - { - /* descending sequence */ - if ((minv < 0 && next < minv - incby) || - (minv >= 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 = maxv; - } - else - next += incby; - } - fetch--; - if (rescnt < cache) - { - log--; - rescnt++; - last = next; - if (rescnt == 1) /* if it's first result - */ - result = next; /* it's what to return */ - } - } - - log -= fetch; /* adjust for any unfetched numbers */ - Assert(log >= 0); + /* retrieve next value from the access method */ + result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -832,69 +481,6 @@ nextval_internal(Oid relid, bool check_permissions) last_used_seq = elm; - /* - * 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 = next; - seq->is_called = true; - seq->log_cnt = 0; - - xlrec.locator = seqrel->rd_locator; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - /* Now update sequence tuple to the intended final state */ - seq->last_value = last; /* last fetched number */ - seq->is_called = true; - seq->log_cnt = log; /* how much is logged */ - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); - sequence_close(seqrel, NoLock); return result; @@ -984,9 +570,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, @@ -1020,9 +603,6 @@ do_setval(Oid relid, int64 next, bool iscalled) */ PreventCommandIfParallelMode("setval()"); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - if ((next < minv) || (next > maxv)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), @@ -1044,37 +624,8 @@ do_setval(Oid relid, int64 next, bool iscalled) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - /* ready to change the on-disk (or really, in-buffer) tuple */ - START_CRIT_SECTION(); - - seq->last_value = next; /* last fetched number */ - seq->is_called = iscalled; - seq->log_cnt = 0; - - MarkBufferDirty(buf); - - /* XLOG stuff */ - if (RelationNeedsWAL(seqrel)) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - Page page = BufferGetPage(buf); - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = seqrel->rd_locator; - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); + /* Call the access method callback */ + seq_local_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1215,62 +766,6 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) } -/* - * Given an opened sequence relation, lock the page buffer and find the tuple - * - * *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 = ReadBuffer(rel, 0); - LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); - - page = BufferGetPage(*buf); - sm = (sequence_magic *) PageGetSpecialPointer(page); - - if (sm->magic != SEQ_MAGIC) - elog(ERROR, "bad magic number in sequence \"%s\": %08X", - RelationGetRelationName(rel), sm->magic); - - lp = PageGetItemId(page, FirstOffsetNumber); - Assert(ItemIdIsNormal(lp)); - - /* Note we currently only bother to set these two fields of *seqdatatuple */ - seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); - seqdatatuple->t_len = 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) != InvalidTransactionId) - { - HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); - seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; - seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; - MarkBufferDirtyHint(*buf, true); - } - - seq = (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 @@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; + bool is_called; + int64 last_value; - seq = read_seq_tuple(seqrel, &buf, &seqtuple); + seq_local_get_state(seqrel, &last_value, &is_called); - values[0] = Int64GetDatum(seq->last_value); - values[1] = BoolGetDatum(seq->is_called); - - UnlockReleaseBuffer(buf); + values[0] = Int64GetDatum(last_value); + values[1] = BoolGetDatum(is_called); } else memset(isnull, true, sizeof(isnull)); @@ -1908,17 +1400,9 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; - - seq = read_seq_tuple(seqrel, &buf, &seqtuple); - - is_called = seq->is_called; - result = seq->last_value; - - UnlockReleaseBuffer(buf); + seq_local_get_state(seqrel, &result, &is_called); } + sequence_close(seqrel, NoLock); if (is_called) @@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - -void -seq_redo(XLogReaderState *record) -{ - XLogRecPtr lsn = record->EndRecPtr; - uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - Buffer buffer; - Page page; - Page localpage; - char *item; - Size itemsz; - xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record); - sequence_magic *sm; - - if (info != XLOG_SEQ_LOG) - elog(PANIC, "seq_redo: unknown op code %u", info); - - buffer = XLogInitBufferForRedo(record, 0); - page = (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 = (Page) palloc(BufferGetPageSize(buffer)); - - PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(localpage); - sm->magic = SEQ_MAGIC; - - item = (char *) xlrec + sizeof(xl_seq_rec); - itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec); - - if (PageAddItem(localpage, (Item) item, itemsz, - FirstOffsetNumber, false, false) == 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. */ @@ -1992,14 +1425,3 @@ ResetSequenceCaches(void) last_used_seq = 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..8d1195de26 100644 --- a/src/bin/pg_waldump/.gitignore +++ b/src/bin/pg_waldump/.gitignore @@ -10,13 +10,13 @@ /gistdesc.c /hashdesc.c /heapdesc.c +/seqlocaldesc.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..2fccc7a4e2 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/seqlocalam.h" #include "access/multixact.h" #include "access/nbtxlog.h" #include "access/rmgr.h" diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl index 578e473139..0b911048e1 100644 --- a/src/bin/pg_waldump/t/001_basic.pl +++ b/src/bin/pg_waldump/t/001_basic.pl @@ -67,7 +67,7 @@ Btree Hash Gin Gist -Sequence +SequenceLocal SPGist BRIN CommitTs -- 2.45.2 [text/x-diff] v10-0004-Sequence-access-methods-backend-support.patch (63.9K, ../../[email protected]/5-v10-0004-Sequence-access-methods-backend-support.patch) download | inline diff: From a63c7d6672159d5e4c5fb7b0b7e6f456f67e48ed Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Wed, 4 Dec 2024 08:09:46 +0900 Subject: [PATCH v10 4/7] Sequence access methods - backend support The "seqlocal" sequence AM is now plugged in as a handler in the relcache, and a set of callbacks in sequenceam.h. --- src/include/access/seqlocalam.h | 15 -- src/include/access/sequenceam.h | 181 ++++++++++++++++++ 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/meson.build | 1 + src/backend/access/sequence/seqlocalam.c | 41 +++- 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 | 23 +-- 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/psql/describe.c | 2 + src/bin/psql/tab-complete.in.c | 4 +- src/test/regress/expected/create_am.out | 55 ++++-- src/test/regress/expected/opr_sanity.out | 12 ++ src/test/regress/expected/psql.out | 96 +++++----- src/test/regress/expected/type_sanity.out | 12 +- src/test/regress/sql/create_am.sql | 24 ++- src/test/regress/sql/opr_sanity.sql | 10 + src/test/regress/sql/type_sanity.sql | 12 +- src/tools/pgindent/typedefs.list | 5 +- 40 files changed, 695 insertions(+), 182 deletions(-) create mode 100644 src/include/access/sequenceam.h create mode 100644 src/backend/access/sequence/sequenceamapi.c diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h index 225fb9a2cb..21936511ac 100644 --- a/src/include/access/seqlocalam.h +++ b/src/include/access/seqlocalam.h @@ -15,7 +15,6 @@ #include "access/xlogreader.h" #include "storage/relfilelocator.h" -#include "utils/rel.h" /* XLOG stuff */ #define XLOG_SEQ_LOCAL_LOG 0x00 @@ -41,18 +40,4 @@ extern void seq_local_desc(StringInfo buf, XLogReaderState *record); extern const char *seq_local_identify(uint8 info); extern void seq_local_mask(char *page, BlockNumber blkno); -/* access routines */ -extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last); -extern const char *seq_local_get_table_am(void); -extern void seq_local_init(Relation rel, int64 last_value, bool is_called); -extern void seq_local_setval(Relation rel, int64 next, bool iscalled); -extern void seq_local_reset(Relation rel, int64 startv, bool is_called, - bool reset_state); -extern void seq_local_get_state(Relation rel, int64 *last_value, - bool *is_called); -extern void seq_local_change_persistence(Relation rel, - char newrelpersistence); - #endif /* SEQLOCALAM_H */ diff --git a/src/include/access/sequenceam.h b/src/include/access/sequenceam.h new file mode 100644 index 0000000000..ac48c8b468 --- /dev/null +++ b/src/include/access/sequenceam.h @@ -0,0 +1,181 @@ +/*------------------------------------------------------------------------- + * + * sequenceam.h + * POSTGRES sequence access method definitions. + * + * + * Portions Copyright (c) 1996-2024, 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 "seqlocal" + +/* 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 value + * allocated. The result returned is the next value of the sequence computed. + */ +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); + +#endif /* SEQUENCEAM_H */ diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat index db87490282..ba9766fae4 100644 --- a/src/include/catalog/pg_am.dat +++ b/src/include/catalog/pg_am.dat @@ -15,6 +15,9 @@ { oid => '2', oid_symbol => 'HEAP_TABLE_AM_OID', descr => 'heap table access method', amname => 'heap', amhandler => 'heap_tableam_handler', amtype => 't' }, +{ oid => '8051', oid_symbol => 'LOCAL_SEQUENCE_AM_OID', + descr => 'local sequence access method', + amname => 'seqlocal', amhandler => 'seq_local_sequenceam_handler', amtype => 's' }, { oid => '403', oid_symbol => 'BTREE_AM_OID', descr => 'b-tree index access method', amname => 'btree', amhandler => 'bthandler', amtype => '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 */ #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) == RELKIND_TOASTVALUE || \ (relkind) == RELKIND_MATVIEW) +/* + * Relation kinds with a sequence access method (rd_sequenceam). + */ +#define RELKIND_HAS_SEQUENCE_AM(relkind) \ + ((relkind) == RELKIND_SEQUENCE) + extern int errdetail_relkind_not_supported(char relkind); #endif /* EXPOSE_TO_CLIENT_CODE */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 9575524007..2d205649f6 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -913,6 +913,12 @@ prorettype => 'table_am_handler', proargtypes => 'internal', prosrc => 'heap_tableam_handler' }, +# Sequence access method handlers +{ oid => '8209', descr => 'local sequence access method handler', + proname => 'seq_local_sequenceam_handler', provolatile => 'v', + prorettype => 'sequence_am_handler', proargtypes => 'internal', + prosrc => 'seq_local_sequenceam_handler' }, + # Index access method handlers { oid => '330', descr => 'btree index access method handler', proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler', @@ -7765,6 +7771,13 @@ { oid => '327', descr => 'I/O', proname => 'index_am_handler_out', prorettype => 'cstring', proargtypes => 'index_am_handler', prosrc => 'index_am_handler_out' }, +{ oid => '8207', descr => 'I/O', + proname => 'sequence_am_handler_in', proisstrict => 'f', + prorettype => 'sequence_am_handler', proargtypes => 'cstring', + prosrc => 'sequence_am_handler_in' }, +{ oid => '8208', descr => 'I/O', + proname => 'sequence_am_handler_out', prorettype => 'cstring', + proargtypes => 'sequence_am_handler', prosrc => 'sequence_am_handler_out' }, { oid => '3311', descr => 'I/O', proname => 'tsm_handler_in', proisstrict => 'f', prorettype => 'tsm_handler', proargtypes => 'cstring', prosrc => 'tsm_handler_in' }, diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat index ceff66ccde..0eba013687 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -627,6 +627,12 @@ typcategory => 'P', typinput => 'index_am_handler_in', typoutput => 'index_am_handler_out', typreceive => '-', typsend => '-', typalign => 'i' }, +{ oid => '8210', + descr => 'pseudo-type for the result of a sequence AM handler function', + typname => 'sequence_am_handler', typlen => '4', typbyval => 't', + typtype => 'p', typcategory => 'P', typinput => 'sequence_am_handler_in', + typoutput => 'sequence_am_handler_out', typreceive => '-', typsend => '-', + typalign => 'i' }, { oid => '3310', descr => 'pseudo-type for the result of a tablesample method function', typname => 'tsm_handler', typlen => '4', typbyval => 't', typtype => '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); diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.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" -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); -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 = [ '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 26f0b45c7e..9aff80ea34 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3148,6 +3148,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; diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 5813dba0a2..f4a49969b8 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, 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 **extra, + 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; + /* + * 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/sequence/Makefile index a15ceec1c0..62006165a1 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = seqlocalam.o seqlocalxlog.o sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o sequenceamapi.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index 4d24f900b6..ea91990552 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -4,4 +4,5 @@ backend_sources += files( 'seqlocalam.c', 'seqlocalxlog.c', 'sequence.c', + 'sequenceamapi.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c index b0a0d72966..2e524c9e99 100644 --- a/src/backend/access/sequence/seqlocalam.c +++ b/src/backend/access/sequence/seqlocalam.c @@ -17,6 +17,7 @@ #include "access/multixact.h" #include "access/seqlocalam.h" +#include "access/sequenceam.h" #include "access/xact.h" #include "access/xloginsert.h" #include "access/xlogutils.h" @@ -24,6 +25,7 @@ #include "commands/tablecmds.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "utils/builtins.h" /* @@ -230,10 +232,10 @@ fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) * Allocate a new value for a local sequence, based on the sequence * configuration. */ -int64 +static int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last) + int64 minv, int64 cache, bool cycle, + int64 *last) { int64 result; int64 fetch; @@ -417,7 +419,7 @@ seq_local_nextval(Relation rel, int64 incby, int64 maxv, * * Return the table access method used by this sequence. */ -const char * +static const char * seq_local_get_table_am(void) { return "heap"; @@ -432,7 +434,7 @@ seq_local_get_table_am(void) * inserted after the relation has been created, filling in its heap * table. */ -void +static void seq_local_init(Relation rel, int64 last_value, bool is_called) { Datum value[SEQ_LOCAL_COL_LASTCOL]; @@ -499,7 +501,7 @@ seq_local_init(Relation rel, int64 last_value, bool is_called) * * Callback for setval(). */ -void +static void seq_local_setval(Relation rel, int64 next, bool iscalled) { Buffer buf; @@ -547,7 +549,7 @@ seq_local_setval(Relation rel, int64 next, bool iscalled) * Perform a hard reset on the local sequence, rewriting its heap data * entirely. */ -void +static void seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) { Form_pg_seq_local_data seq; @@ -600,7 +602,7 @@ seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) * * Retrieve the state of a local sequence. */ -void +static void seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) { Buffer buf; @@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) * * Persistence change for the local sequence Relation. */ -void +static void seq_local_change_persistence(Relation rel, char newrelpersistence) { Buffer buf; @@ -632,3 +634,24 @@ seq_local_change_persistence(Relation rel, char newrelpersistence) fill_seq_with_data(rel, &seqdatatuple); UnlockReleaseBuffer(buf); } + +/* ------------------------------------------------------------------------ + * Definition of the local sequence access method. + * ------------------------------------------------------------------------ + */ +static const SequenceAmRoutine seq_local_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = seq_local_get_table_am, + .init = seq_local_init, + .nextval = seq_local_nextval, + .setval = seq_local_setval, + .reset = seq_local_reset, + .get_state = seq_local_get_state, + .change_persistence = seq_local_change_persistence +}; + +Datum +seq_local_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&seq_local_methods); +} diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/sequence.c index d2cf95aadc..8669b20c26 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 sequences - * (in contrast to other relation types like indexes). + * (in contrast to other relation types like indexes) that are independent + * of individual sequence access methods. * *------------------------------------------------------------------------- */ diff --git a/src/backend/access/sequence/sequenceamapi.c b/src/backend/access/sequence/sequenceamapi.c new file mode 100644 index 0000000000..dd1a60d827 --- /dev/null +++ b/src/backend/access/sequence/sequenceamapi.c @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------- + * + * sequenceamapi.c + * general sequence access method routines + * + * Portions Copyright (c) 1996-2024, 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 = 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 = OidFunctionCall0(amhandler); + routine = (SequenceAmRoutine *) DatumGetPointer(datum); + + if (routine == NULL || !IsA(routine, SequenceAmRoutine)) + elog(ERROR, "sequence access method handler %u did not return a SequenceAmRoutine 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 != NULL); + Assert(routine->init != NULL); + Assert(routine->nextval != NULL); + Assert(routine->setval != NULL); + Assert(routine->reset != NULL); + Assert(routine->get_state != NULL); + Assert(routine->change_persistence != 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 = SearchSysCache1(AMOID, + ObjectIdGetDatum(amoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", amoid); + aform = (Form_pg_am) GETSTRUCT(tuple); + Assert(aform->amtype == AMTYPE_SEQUENCE); + amhandleroid = 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 == '\0') + { + GUC_check_errdetail("%s cannot be empty.", + "default_sequence_access_method"); + return false; + } + + if (strlen(*newval) >= 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 != InvalidOid) + { + if (!OidIsValid(get_sequence_am_oid(*newval, true))) + { + /* + * When source == 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 == 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 d7b88b61dc..cd0cb4784b 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -1473,9 +1473,13 @@ 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. Partitioned tables may not have an * access method set. + * + * Sequences and tables are created with their access method ID + * given by the caller of this function. */ if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) || - (relkind == RELKIND_PARTITIONED_TABLE && OidIsValid(accessmtd))) + (relkind == RELKIND_PARTITIONED_TABLE && OidIsValid(accessmtd)) || + 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 @@ #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); } +/* + * 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 = INDEX_AM_HANDLEROID; break; + case AMTYPE_SEQUENCE: + expectedType = SEQUENCE_AM_HANDLEROID; + break; case AMTYPE_TABLE: expectedType = TABLE_AM_HANDLEROID; break; diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 1cad06da66..a65e93bbee 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,10 +16,10 @@ #include "access/bufmask.h" #include "access/htup_details.h" -#include "access/seqlocalam.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 = NIL; stmt->constraints = NIL; stmt->options = NIL; + stmt->accessMethod = seq->accessMethod ? pstrdup(seq->accessMethod) : NULL; stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; @@ -173,7 +174,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) rel = sequence_open(seqoid, AccessExclusiveLock); /* now initialize the sequence table structure and its data */ - seq_local_init(rel, last_value, is_called); + sequence_init(rel, last_value, is_called); /* process OWNED BY if given */ if (owned_by) @@ -241,7 +242,7 @@ ResetSequence(Oid seq_relid) ReleaseSysCache(pgstuple); /* Sequence state is forcibly reset here. */ - seq_local_reset(seq_rel, startv, false, true); + sequence_reset(seq_rel, startv, false, true); /* 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 = (Form_pg_sequence) GETSTRUCT(seqtuple); /* Read sequence data */ - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); /* 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(); - seq_local_reset(seqrel, last_value, is_called, reset_state); + sequence_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -353,7 +354,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - seq_local_change_persistence(seqrel, newrelpersistence); + sequence_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -470,8 +471,8 @@ nextval_internal(Oid relid, bool check_permissions) ReleaseSysCache(pgstuple); /* retrieve next value from the access method */ - result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, - &last); + result = sequence_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -625,7 +626,7 @@ do_setval(Oid relid, int64 next, bool iscalled) GetTopTransactionId(); /* Call the access method callback */ - seq_local_setval(seqrel, next, iscalled); + sequence_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS) bool is_called; int64 last_value; - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); values[0] = Int64GetDatum(last_value); values[1] = BoolGetDatum(is_called); @@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - seq_local_get_state(seqrel, &result, &is_called); + sequence_get_state(seqrel, &result, &is_called); } sequence_close(seqrel, NoLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 390b029394..858c4aded9 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" @@ -983,14 +984,18 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, } /* - * 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 != NULL) { - Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE); - accessMethodId = get_table_am_oid(stmt->accessMethod, false); + Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE || + RELKIND_HAS_SEQUENCE_AM(relkind)); + if (RELKIND_HAS_SEQUENCE_AM(relkind)) + accessMethodId = get_sequence_am_oid(stmt->accessMethod, false); + else + accessMethodId = get_table_am_oid(stmt->accessMethod, false); } else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE) { @@ -1003,6 +1008,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId)) accessMethodId = get_table_am_oid(default_table_access_method, false); } + else if (RELKIND_HAS_SEQUENCE_AM(relkind)) + { + accessMethodId = get_sequence_am_oid(default_sequence_access_method, false); + } /* * 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 = \ 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 = 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 = 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 67eb96396a..b7a8948ab3 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -377,6 +377,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <str> copy_file_name access_method_clause attr_name table_access_method_clause name cursor_name file_name + sequence_access_method_clause cluster_index_specification %type <list> func_name handler_name qual_Op qual_all_Op subquery_Op @@ -4884,23 +4885,26 @@ RefreshMatViewStmt: CreateSeqStmt: CREATE OptTemp SEQUENCE qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); - $4->relpersistence = $2; n->sequence = $4; n->options = $5; + n->accessMethod = $6; n->ownerId = InvalidOid; n->if_not_exists = false; $$ = (Node *) n; } | CREATE OptTemp SEQUENCE IF_P NOT EXISTS qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); $7->relpersistence = $2; n->sequence = $7; n->options = $8; + n->accessMethod = $9; n->ownerId = InvalidOid; n->if_not_exists = true; $$ = (Node *) n; @@ -4937,6 +4941,11 @@ OptParenthesizedSeqOptList: '(' SeqOptList ')' { $$ = $2; } | /*EMPTY*/ { $$ = NIL; } ; +sequence_access_method_clause: + USING name { $$ = $2; } + | /*EMPTY*/ { $$ = NULL; } + ; + SeqOptList: SeqOptElem { $$ = list_make1($1); } | SeqOptList SeqOptElem { $$ = lappend($1, $2); } ; @@ -5940,6 +5949,7 @@ CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name am_type: INDEX { $$ = AMTYPE_INDEX; } + | SEQUENCE { $$ = AMTYPE_SEQUENCE; } | TABLE { $$ = AMTYPE_TABLE; } ; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 0f324ee4e3..3b0379d628 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" @@ -518,6 +519,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, seqstmt->sequence = makeRangeVar(snamespace, sname, -1); seqstmt->sequence->relpersistence = seqpersistence; seqstmt->options = seqoptions; + seqstmt->accessMethod = NULL; /* * 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/pseudotypes.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/relcache.c index d0892cee24..0008744458 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" @@ -302,6 +304,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 == RELKIND_INDEX || relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) RelationInitIndexAccessInfo(relation); - else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || - relation->rd_rel->relkind == RELKIND_SEQUENCE) + else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)) RelationInitTableAccessMethod(relation); else if (relation->rd_rel->relkind == 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 == InvalidOid); @@ -1811,17 +1815,9 @@ RelationInitTableAccessMethod(Relation relation) HeapTuple tuple; Form_pg_am aform; - if (relation->rd_rel->relkind == 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 == InvalidOid); - relation->rd_amhandler = 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); } +/* + * 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 != InvalidOid); + relation->rd_amhandler = GetSequenceAmRoutineId(relation->rd_rel->relam); + + /* + * Now we can fetch the sequence AM's API struct. + */ + relation->rd_sequenceam = GetSequenceAmRoutine(relation->rd_amhandler); + + /* + * From the sequence AM, set its expected table access method. + */ + tableam_name = sequence_get_table_am(relation); + tableam_oid = get_table_am_oid(tableam_name, false); + + tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(tableam_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", + tableam_oid); + aform = (Form_pg_am) GETSTRUCT(tuple); + tableam_handler = aform->amhandler; + ReleaseSysCache(tuple); + + relation->rd_tableam = GetTableAmRoutine(tableam_handler); +} + /* * formrdesc * @@ -3667,14 +3706,17 @@ RelationBuildLocalRelation(const char *relname, rel->rd_rel->relam = accessmtd; /* - * 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); - if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(relkind)) RelationInitTableAccessMethod(rel); + else if (relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); /* * Leave index access method uninitialized, because the pg_index row has @@ -4298,13 +4340,21 @@ RelationCacheInitializePhase3(void) /* Reload tableam data if needed */ if (relation->rd_tableam == NULL && - (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE)) + (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))) { RelationInitTableAccessMethod(relation); Assert(relation->rd_tableam != NULL); restart = true; } + else if (relation->rd_sequenceam == NULL && + relation->rd_rel->relkind == RELKIND_SEQUENCE) + { + RelationInitSequenceAccessMethod(relation); + Assert(relation->rd_sequenceam != NULL); + + restart = true; + } /* Release hold on the relation */ RelationDecrementReferenceCount(relation); @@ -6320,8 +6370,10 @@ load_relcache_init_file(bool shared) nailed_rels++; /* Load table AM data */ - if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || rel->rd_rel->relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind)) RelationInitTableAccessMethod(rel); + else if (rel->rd_rel->relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); Assert(rel->rd_index == NULL); Assert(rel->rd_indextuple == NULL); @@ -6333,6 +6385,7 @@ load_relcache_init_file(bool shared) Assert(rel->rd_supportinfo == NULL); Assert(rel->rd_indoption == NULL); Assert(rel->rd_indcollation == NULL); + Assert(rel->rd_sequenceam == NULL); Assert(rel->rd_opcoptions == NULL); } diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 8cf1afbad2..c28f85e7bf 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" @@ -4204,6 +4205,17 @@ struct config_string ConfigureNamesString[] = check_default_table_access_method, NULL, NULL }, + { + {"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default sequence access method for new sequences."), + 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/utils/misc/postgresql.conf.sample index a2ac7575ca..a049a7d424 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -706,6 +706,7 @@ # error #search_path = '"$user", public' # schema names #row_security = on +#default_sequence_access_method = 'seqlocal' #default_table_access_method = 'heap' #default_tablespace = '' # a tablespace name, '' uses the default #default_toast_compression = 'pglz' # 'pglz' or 'lz4' diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 2657abdc72..0428d44793 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -167,10 +167,12 @@ describeAccessMethods(const char *pattern, bool verbose) "SELECT amname AS \"%s\",\n" " CASE amtype" " WHEN " CppAsString2(AMTYPE_INDEX) " THEN '%s'" + " WHEN " CppAsString2(AMTYPE_SEQUENCE) " THEN '%s'" " WHEN " CppAsString2(AMTYPE_TABLE) " THEN '%s'" " END AS \"%s\"", gettext_noop("Name"), gettext_noop("Index"), + gettext_noop("Sequence"), gettext_noop("Table"), gettext_noop("Type")); diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index bbd08770c3..45dbae55a2 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -2532,7 +2532,7 @@ match_previous_words(int pattern_id, 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 <name> AS */ else if (TailMatches("ALTER", "SEQUENCE", MatchAny, "AS")) COMPLETE_WITH_CS("smallint", "integer", "bigint"); @@ -3557,7 +3557,7 @@ match_previous_words(int pattern_id, 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/expected/create_am.out index c1a9515725..784870e603 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; @@ -514,9 +509,12 @@ CREATE TABLE tableam_parted_heapx (a text, b int) PARTITION 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 = 'seqlocal'; 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; @@ -533,18 +531,18 @@ FROM pg_class AS pc LEFT JOIN pg_am AS pa ON (pa.oid = pc.relam) WHERE pc.relname LIKE 'tableam_%_heapx' ORDER BY 3, 1, 2; - relkind | amname | relname ----------+--------+----------------------------- - f | | tableam_fdw_heapx - r | heap2 | tableam_parted_1_heapx - r | heap | tableam_parted_2_heapx - p | | tableam_parted_heapx - S | | tableam_seq_heapx - r | heap2 | tableam_tbl_heapx - r | heap2 | tableam_tblas_heapx - m | heap2 | tableam_tblmv_heapx - r | heap2 | tableam_tblselectinto_heapx - v | | tableam_view_heapx + relkind | amname | relname +---------+----------+----------------------------- + f | | tableam_fdw_heapx + r | heap2 | tableam_parted_1_heapx + r | heap | tableam_parted_2_heapx + p | | tableam_parted_heapx + S | seqlocal | tableam_seq_heapx + r | heap2 | tableam_tbl_heapx + r | heap2 | tableam_tblas_heapx + m | heap2 | tableam_tblmv_heapx + r | heap2 | tableam_tblselectinto_heapx + v | | tableam_view_heapx (10 rows) -- don't want to keep those tables, nor the default @@ -574,3 +572,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_dump support +-- Checks for sequence access methods +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_local_sequenceam_handler; +-- Create and use sequence +CREATE SEQUENCE test_seqam USING local2; +SELECT nextval('test_seqam'::regclass); + nextval +--------- + 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/expected/opr_sanity.out index 34a32bd11d..8bc2238e60 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1935,6 +1935,18 @@ WHERE p1.oid = a1.amhandler AND a1.amtype = 't' AND -----+--------+-----+--------- (0 rows) +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + oid | amname | oid | proname +-----+--------+-----+--------- +(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 36dc31c16c..ac4848833e 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -5064,31 +5064,33 @@ Indexes: -- check printing info about access methods \dA List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA * List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA h* List of access methods @@ -5113,32 +5115,34 @@ List of access methods \dA: extra argument "bar" ignored \dA+ - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ * - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ h* List of access methods diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out index 88d8f6c32d..de2796ae05 100644 --- a/src/test/regress/expected/type_sanity.out +++ b/src/test/regress/expected/type_sanity.out @@ -507,21 +507,21 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p', 'I') OR -----+--------- (0 rows) --- All tables, indexes, partitioned indexes and matviews should have an --- access method. +-- All tables, indexes, partitioned indexes, matviews and sequences should have +-- an access method. SELECT c1.oid, c1.relname FROM pg_class as c1 -WHERE c1.relkind NOT IN ('S', 'v', 'f', 'c', 'p') and +WHERE c1.relkind NOT IN ('v', 'f', 'c', 'p') and c1.relam = 0; oid | relname -----+--------- (0 rows) --- Conversely, sequences, views, foreign tables, types and partitioned --- tables shouldn't have them. +-- Conversely, views, foreign tables, types and partitioned tables +-- shouldn't have them. SELECT c1.oid, c1.relname FROM pg_class as c1 -WHERE c1.relkind IN ('S', 'v', 'f', 'c', 'p') and +WHERE c1.relkind IN ('v', 'f', 'c', 'p') and c1.relam != 0; oid | relname -----+--------- diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql index 754fe0c694..76a91cf8dd 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 FROM tableam_tbl_heap2; -- CREATE VIEW doesn't support USING CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2; --- 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; @@ -327,9 +324,13 @@ CREATE TABLE tableam_parted_1_heapx PARTITION OF tableam_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; --- 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 = 'seqlocal'; 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; @@ -365,3 +366,16 @@ CREATE FOREIGN TABLE fp PARTITION OF tableam_parted_a_heap2 DEFAULT SERVER x; DROP ACCESS METHOD heap2; -- we intentionally leave the objects created above alive, to verify pg_dump support + +-- Checks for sequence access methods + +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_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 = a1.amhandler AND a1.amtype = 't' AND OR p1.pronargs != 1 OR p1.proargtypes[0] != 'internal'::regtype); +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + -- **************** pg_amop **************** -- Look for illegal values in pg_amop fields diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql index e88d6cbe49..1ff8580a79 100644 --- a/src/test/regress/sql/type_sanity.sql +++ b/src/test/regress/sql/type_sanity.sql @@ -364,18 +364,18 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p', 'I') OR relpersistence NOT IN ('p', 'u', 't') OR relreplident NOT IN ('d', 'n', 'f', 'i'); --- All tables, indexes, partitioned indexes and matviews should have an --- access method. +-- All tables, indexes, partitioned indexes, matviews and sequences should have +-- an access method. SELECT c1.oid, c1.relname FROM pg_class as c1 -WHERE c1.relkind NOT IN ('S', 'v', 'f', 'c', 'p') and +WHERE c1.relkind NOT IN ('v', 'f', 'c', 'p') and c1.relam = 0; --- Conversely, sequences, views, foreign tables, types and partitioned --- tables shouldn't have them. +-- Conversely, views, foreign tables, types and partitioned tables +-- shouldn't have them. SELECT c1.oid, c1.relname FROM pg_class as c1 -WHERE c1.relkind IN ('S', 'v', 'f', 'c', 'p') and +WHERE c1.relkind IN ('v', 'f', 'c', 'p') and c1.relam != 0; -- Indexes and partitioned indexes should have AMs of type 'i'. diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 2d4c870423..9d08f9cd38 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2585,6 +2585,7 @@ SeqScanState SeqTable SeqTableData SeqType +SequenceAmRoutine SequenceItem SerCommitSeqNo SerialControl @@ -3612,6 +3613,7 @@ list_sort_comparator loc_chunk local_relopt local_relopts +local_sequence_magic local_source local_ts_iter local_ts_radix_tree @@ -3896,7 +3898,6 @@ scram_state_enum script_error_callback_arg security_class_t sem_t -sepgsql_context_info_t sequence_magic set_join_pathlist_hook_type set_rel_pathlist_hook_type @@ -4120,6 +4121,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 @@ -4131,7 +4133,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 -- 2.45.2 [text/x-diff] v10-0005-Sequence-access-methods-dump-restore-support.patch (21.7K, ../../[email protected]/6-v10-0005-Sequence-access-methods-dump-restore-support.patch) download | inline diff: From 696f6a4471aabd54cbbec8c85e179a335df8f32b Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Mon, 26 Aug 2024 13:25:19 +0900 Subject: [PATCH v10 5/7] 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 | 47 +++++++++++++++----- 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 | 49 +++++++++++++++++++++ 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, 207 insertions(+), 11 deletions(-) diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index f0f19bb0b2..2b8298f194 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -96,6 +96,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 @@ -186,6 +187,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_backup_archiver.c index 59e7941a0c..f3b5cf6e57 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -176,6 +176,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) dopt->outputSuperuser = ropt->superuser; dopt->outputCreateDB = ropt->createDB; dopt->outputNoOwner = ropt->noOwner; + dopt->outputNoSequenceAm = ropt->noSequenceAm; dopt->outputNoTableAm = ropt->noTableAm; dopt->outputNoTablespaces = ropt->noTablespace; dopt->disable_triggers = ropt->disable_triggers; @@ -1228,6 +1229,7 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId, newToc->tag = pg_strdup(opts->tag); newToc->namespace = opts->namespace ? pg_strdup(opts->namespace) : NULL; newToc->tablespace = opts->tablespace ? pg_strdup(opts->tablespace) : NULL; + newToc->sequenceam = opts->sequenceam ? pg_strdup(opts->sequenceam) : NULL; newToc->tableam = opts->tableam ? pg_strdup(opts->tableam) : NULL; newToc->relkind = opts->relkind; newToc->owner = opts->owner ? pg_strdup(opts->owner) : NULL; @@ -2383,6 +2385,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, AH->currUser = NULL; /* unknown */ AH->currSchema = NULL; /* ditto */ + AH->currSequenceAm = NULL; /* ditto */ AH->currTablespace = NULL; /* ditto */ AH->currTableAm = NULL; /* ditto */ @@ -2612,6 +2615,7 @@ WriteToc(ArchiveHandle *AH) WriteStr(AH, te->copyStmt); WriteStr(AH, te->namespace); WriteStr(AH, te->tablespace); + WriteStr(AH, te->sequenceam); WriteStr(AH, te->tableam); WriteInt(AH, te->relkind); WriteStr(AH, te->owner); @@ -2716,6 +2720,9 @@ ReadToc(ArchiveHandle *AH) if (AH->version >= K_VERS_1_10) te->tablespace = ReadStr(AH); + if (AH->version >= K_VERS_1_17) + te->sequenceam = ReadStr(AH); + if (AH->version >= K_VERS_1_14) te->tableam = ReadStr(AH); @@ -3371,6 +3378,9 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname) free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; + free(AH->currTableAm); AH->currTableAm = NULL; @@ -3533,6 +3543,57 @@ _selectTablespace(ArchiveHandle *AH, const char *tablespace) destroyPQExpBuffer(qry); } +/* + * Set the proper default_sequence_access_method value for the sequence. + */ +static void +_selectSequenceAccessMethod(ArchiveHandle *AH, const char *sequenceam) +{ + RestoreOptions *ropt = AH->public.ropt; + PQExpBuffer cmd; + const char *want, + *have; + + /* do nothing in --no-sequence-access-method mode */ + if (ropt->noSequenceAm) + return; + + have = AH->currSequenceAm; + want = sequenceam; + + if (!want) + return; + + if (have && strcmp(want, have) == 0) + return; + + cmd = createPQExpBuffer(); + appendPQExpBuffer(cmd, + "SET default_sequence_access_method = %s;", + fmtId(want)); + + if (RestoringToDB(AH)) + { + PGresult *res; + + res = PQexec(AH->connection, cmd->data); + + if (!res || PQresultStatus(res) != 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 = pg_strdup(want); +} + /* * Set the proper default_table_access_method value for the table. */ @@ -3742,6 +3803,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData) _becomeOwner(AH, te); _selectOutputSchema(AH, te->namespace); _selectTablespace(AH, te->tablespace); + _selectSequenceAccessMethod(AH, te->sequenceam); if (te->relkind != RELKIND_PARTITIONED_TABLE) _selectTableAccessMethod(AH, te->tableam); @@ -4266,6 +4328,8 @@ restore_toc_entries_prefork(ArchiveHandle *AH, TocEntry *pending_list) AH->currUser = NULL; free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; free(AH->currTablespace); AH->currTablespace = NULL; free(AH->currTableAm); @@ -5005,6 +5069,7 @@ CloneArchive(ArchiveHandle *AH) clone->connCancel = NULL; clone->currUser = NULL; clone->currSchema = NULL; + clone->currSequenceAm = NULL; clone->currTableAm = NULL; clone->currTablespace = NULL; @@ -5064,6 +5129,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_backup_archiver.h index ce5ed1dd39..cdfebcd340 100644 --- a/src/bin/pg_dump/pg_backup_archiver.h +++ b/src/bin/pg_dump/pg_backup_archiver.h @@ -71,10 +71,11 @@ #define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0) /* BLOB METADATA entries * and multiple BLOBS, * relkind */ +#define K_VERS_1_17 MAKE_ARCHIVE_VERSION(1, 17, 0) /* add sequenceam */ /* 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_VERS_REV) @@ -322,6 +323,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 */ @@ -353,6 +355,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 relkind; /* relation kind, only for TABLE tags */ char *owner; @@ -394,6 +397,7 @@ typedef struct _archiveOpts const char *tag; const char *namespace; const char *tablespace; + const char *sequenceam; const char *tableam; char relkind; const char *owner; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index ec0cdf4ed7..4a970b2e80 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -132,6 +132,7 @@ typedef struct int64 cache; /* cache size */ int64 last_value; /* last value of sequence */ bool is_called; /* whether nextval advances before returning */ + char *seqam; /* access method of sequence */ } SequenceItem; typedef enum OidOptions @@ -481,6 +482,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}, @@ -1106,6 +1108,7 @@ main(int argc, char **argv) ropt->superuser = dopt.outputSuperuser; ropt->createDB = dopt.outputCreateDB; ropt->noOwner = dopt.outputNoOwner; + ropt->noSequenceAm = dopt.outputNoSequenceAm; ropt->noTableAm = dopt.outputNoTableAm; ropt->noTablespace = dopt.outputNoTablespaces; ropt->disable_triggers = dopt.disable_triggers; @@ -1222,6 +1225,7 @@ help(const char *progname) printf(_(" --no-publications do not dump publications\n")); printf(_(" --no-security-labels do not dump security label assignments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); + printf(_(" --no-sequence-access-method do not sequence table access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); @@ -13284,6 +13288,9 @@ dumpAccessMethod(Archive *fout, const AccessMethodInfo *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; @@ -17661,26 +17668,40 @@ collectSequences(Archive *fout) * * Since version 18, we can gather the sequence data in this query with * pg_get_sequence_data(), but we only do so for non-schema-only dumps. + * + * Access methods for sequences are supported since version 18. */ if (fout->remoteVersion < 100000) return; - else if (fout->remoteVersion < 180000 || - (!fout->dopt->dumpData && !fout->dopt->sequence_data)) + else if (fout->remoteVersion < 180000) query = "SELECT seqrelid, format_type(seqtypid, NULL), " "seqstart, seqincrement, " "seqmax, seqmin, " "seqcache, seqcycle, " - "NULL, 'f' " + "NULL, 'f', NULL " "FROM pg_catalog.pg_sequence " "ORDER BY seqrelid"; + else if (!fout->dopt->dumpData && !fout->dopt->sequence_data) + query = "SELECT s.seqrelid, format_type(s.seqtypid, NULL), " + "s.seqstart, s.seqincrement, " + "s.seqmax, s.seqmin, " + "s.seqcache, s.seqcycle, " + "NULL, 'f', a.amname AS seqam " + "FROM pg_catalog.pg_sequence s " + "JOIN pg_class c ON (c.oid = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam) " + "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_get_sequence_data(seqrelid) " + query = "SELECT s.seqrelid, format_type(s.seqtypid, NULL), " + "s.seqstart, s.seqincrement, " + "s.seqmax, s.seqmin, " + "s.seqcache, s.seqcycle, " + "r.last_value, r.is_called, " + "a.amname AS seqam " + "FROM pg_catalog.pg_sequence s " + "JOIN pg_class c ON (c.oid = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam), " + "pg_get_sequence_data(s.seqrelid) r " "ORDER BY seqrelid;"; res = ExecuteSqlQuery(fout, query, PGRES_TUPLES_OK); @@ -17700,6 +17721,10 @@ collectSequences(Archive *fout) 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); + if (!PQgetisnull(res, i, 10)) + sequences[i].seqam = pg_strdup(PQgetvalue(res, i, 10)); + else + sequences[i].seqam = NULL; } PQclear(res); @@ -17771,6 +17796,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) 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); + seq->seqam = NULL; PQclear(res); } @@ -17893,6 +17919,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) ARCHIVE_OPTS(.tag = tbinfo->dobj.name, .namespace = tbinfo->dobj.namespace->dobj.name, .owner = tbinfo->rolname, + .sequenceam = seq->seqam, .description = "SEQUENCE", .section = SECTION_PRE_DATA, .createStmt = query->data, diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index 9a04e51c81..036f756240 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 = 0; static int disable_triggers = 0; static int if_exists = 0; static int inserts = 0; +static int no_sequence_access_method = 0; static int no_table_access_method = 0; static int no_tablespaces = 0; static int use_setsessauth = 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 assignments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); printf(_(" --no-sync do not wait for changes to be written safely to disk\n")); + printf(_(" --no-sequence-access-method do not dump sequence access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c index 88ae39d938..08531eac0a 100644 --- a/src/bin/pg_dump/pg_restore.c +++ b/src/bin/pg_dump/pg_restore.c @@ -67,6 +67,7 @@ main(int argc, char **argv) static int enable_row_security = 0; static int if_exists = 0; static int no_data_for_failed_tables = 0; + static int outputNoSequenceAm = 0; static int outputNoTableAm = 0; static int outputNoTablespaces = 0; static int use_setsessauth = 0; @@ -116,6 +117,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}, @@ -368,6 +370,7 @@ main(int argc, char **argv) opts->disable_triggers = disable_triggers; opts->enable_row_security = enable_row_security; opts->noDataForFailedTables = no_data_for_failed_tables; + opts->noSequenceAm = outputNoSequenceAm; opts->noTableAm = outputNoTableAm; opts->noTablespace = outputNoTablespaces; opts->use_setsessauth = use_setsessauth; @@ -496,6 +499,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 access methods\n")); printf(_(" --no-table-access-method do not restore table access methods\n")); printf(_(" --no-tablespaces do not restore tablespace assignments\n")); printf(_(" --section=SECTION restore named section (pre-data, 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_dump.pl index aa1564cd45..80324986b4 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 = ( 'postgres', ], }, + no_sequence_access_method => { + dump_cmd => [ + 'pg_dump', '--no-sync', + "--file=$tempdir/no_sequence_access_method.sql", + '--no-sequence-access-method', 'postgres', + ], + }, no_table_access_method => { dump_cmd => [ 'pg_dump', '--no-sync', @@ -711,6 +718,7 @@ my %full_runs = ( no_large_objects => 1, no_owner => 1, no_privs => 1, + no_sequence_access_method => 1, no_table_access_method => 1, pg_dumpall_dbprivs => 1, pg_dumpall_exclude => 1, @@ -4534,6 +4542,18 @@ my %tests = ( }, }, + 'CREATE ACCESS METHOD regress_test_sequence_am' => { + create_order => 11, + create_sql => + 'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;', + regexp => qr/^ + \QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;\E + \n/xm, + like => { + %full_runs, section_pre_data => 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 @@ -4562,6 +4582,35 @@ my %tests = ( }, }, + + # 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' => { + create_order => 12, + create_sql => ' + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_0 USING seqlocal; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_1 USING regress_sequence_am; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_2 USING seqlocal;', + regexp => qr/^ + \QSET default_sequence_access_method = 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 => { + %full_runs, %dump_test_schema_runs, section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + no_sequence_access_method => 1, + only_dump_measurement => 1, + }, + }, + 'CREATE MATERIALIZED VIEW regress_pg_dump_matview_am' => { create_order => 13, create_sql => ' diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index d66e901f51..4e0cdc841c 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -1121,6 +1121,23 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + + <para> + This option is ignored when emitting an archive (non-text) output + file. For the archive formats, you can specify the option when you + call <command>pg_restore</command>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml index 014f279258..339d7c7985 100644 --- a/doc/src/sgml/ref/pg_dumpall.sgml +++ b/doc/src/sgml/ref/pg_dumpall.sgml @@ -479,6 +479,17 @@ exclude database <replaceable class="parameter">PATTERN</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml index b8b27e1719..922f69eb4a 100644 --- a/doc/src/sgml/ref/pg_restore.sgml +++ b/doc/src/sgml/ref/pg_restore.sgml @@ -733,6 +733,17 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> -- 2.45.2 [text/x-diff] v10-0006-Sequence-access-methods-core-documentation.patch (9.5K, ../../[email protected]/7-v10-0006-Sequence-access-methods-core-documentation.patch) download | inline diff: From ede1d0343cfb75366c3f3c50c268cb5072344702 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:55:56 +0900 Subject: [PATCH v10 6/7] 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 e0c8325a39..2b49b2fa33 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -9065,6 +9065,22 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-default-sequence-access-method" xreflabel="default_sequence_access_method"> + <term><varname>default_sequence_access_method</varname> (<type>string</type>) + <indexterm> + <primary><varname>default_sequence_access_method</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + This parameter specifies the default sequence access method to use when + creating sequences if the <command>CREATE SEQUENCE</command> + command does not explicitly specify an access method. The default is + <literal>local</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-default-tablespace" xreflabel="default_tablespace"> <term><varname>default_tablespace</varname> (<type>string</type>) <indexterm> diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 66e6dccd4c..4c367bd5fe 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -95,6 +95,7 @@ <!ENTITY planstats SYSTEM "planstats.sgml"> <!ENTITY tableam SYSTEM "tableam.sgml"> <!ENTITY indexam SYSTEM "indexam.sgml"> +<!ENTITY sequenceam SYSTEM "sequenceam.sgml"> <!ENTITY nls SYSTEM "nls.sgml"> <!ENTITY plhandler SYSTEM "plhandler.sgml"> <!ENTITY fdwhandler SYSTEM "fdwhandler.sgml"> diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml index 7be25c5850..cc390e613a 100644 --- a/doc/src/sgml/postgres.sgml +++ b/doc/src/sgml/postgres.sgml @@ -257,6 +257,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 <replaceable class="parameter">name</replaceable> <listitem> <para> This clause specifies the type of access method to define. - Only <literal>TABLE</literal> and <literal>INDEX</literal> - are supported at present. + Only <literal>TABLE</literal>, <literal>SEQUENCE</literal> and + <literal>INDEX</literal> are supported at present. </para> </listitem> </varlistentry> @@ -77,12 +77,15 @@ CREATE ACCESS METHOD <replaceable class="parameter">name</replaceable> declared to take a single argument of type <type>internal</type>, and its return type depends on the type of access method; for <literal>TABLE</literal> access methods, it must - be <type>table_am_handler</type> and for <literal>INDEX</literal> - access methods, it must be <type>index_am_handler</type>. + be <type>table_am_handler</type>; for <literal>INDEX</literal> + access methods, it must be <type>index_am_handler</type>; + for <literal>SEQUENCE</literal>, it must be + <type>sequence_am_handler</type>; 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 <xref linkend="tableam"/> and the index access method - API is described in <xref linkend="indexam"/>. + is described in <xref linkend="tableam"/>, the index access method + API is described in <xref linkend="indexam"/> and the sequence access + method is described in <xref linkend="sequenceam"/>. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/create_sequence.sgml index 1e283f13d1..52c6096e4b 100644 --- a/doc/src/sgml/ref/create_sequence.sgml +++ b/doc/src/sgml/ref/create_sequence.sgml @@ -29,6 +29,7 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceab [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ] + [ USING <replaceable class="parameter">access_method</replaceable> ] </synopsis> </refsynopsisdiv> @@ -263,6 +264,17 @@ SELECT * FROM <replaceable>name</replaceable>; </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>USING</literal> <replaceable class="parameter">access_method</replaceable></term> + <listitem> + <para> + The <literal>USING</literal> option specifies which sequence access + method will be used when generating the sequence numbers. The default + is <literal>local</literal>. + </para> + </listitem> + </varlistentry> </variablelist> </refsect1> 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 @@ +<!-- doc/src/sgml/sequenceam.sgml --> + +<chapter id="sequenceam"> + <title>Sequence Access Method Interface Definition</title> + + <indexterm> + <primary>Sequence Access Method</primary> + </indexterm> + <indexterm> + <primary>sequenceam</primary> + <secondary>Sequence Access Method</secondary> + </indexterm> + + <para> + This chapter explains the interface between the core + <productname>PostgreSQL</productname> system and <firstterm>sequence access + methods</firstterm>, 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. + </para> + + <para> + Each sequence access method is described by a row in the + <link linkend="catalog-pg-am"><structname>pg_am</structname></link> system + catalog. The <structname>pg_am</structname> entry specifies a name and a + <firstterm>handler function</firstterm> for the sequence access method. These + entries can be created and deleted using the + <xref linkend="sql-create-access-method"/> and + <xref linkend="sql-drop-access-method"/> SQL commands. + </para> + + <para> + A sequence access method handler function must be declared to accept a single + argument of type <type>internal</type> and to return the pseudo-type + <type>sequence_am_handler</type>. The argument is a dummy value that simply + 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 + <structname>SequenceAmRoutine</structname>, which contains everything that the + core code needs to know to make use of the sequence access method. The return + value needs to be of server lifetime, which is typically achieved by + defining it as a <literal>static const</literal> variable in global + scope. The <structname>SequenceAmRoutine</structname> struct, also called the + access method's <firstterm>API struct</firstterm>, 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 + <structname>SequenceAmRoutine</structname> structure (with comments inside + 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 <ulink url="https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/include/access/sequenceam.h;hb=HEAD"> + <filename>src/include/access/sequenceam.h</filename></ulink> file. + </para> + + <para> + Currently, the way a sequence access method stores data is fairly + unconstrained, and it is possible to use a predefined + <link linkend="tableam">Table Access Method</link> to store sequence + data. + </para> + + <para> + For crash safety, a sequence access method can use + <link linkend="wal"><acronym>WAL</acronym></link>, or a custom + implementation. + If <acronym>WAL</acronym> is chosen, either + <link linkend="generic-wal">Generic WAL Records</link> can be used, or a + <link linkend="custom-rmgr">Custom WAL Resource Manager</link> can be + implemented. + </para> + + <para> + Any developer of a new <literal>sequence access method</literal> can refer to + the existing <literal>local</literal> implementation present in + <filename>src/backend/access/sequence/local.c</filename> for details of + its implementation. + </para> +</chapter> -- 2.45.2 [text/x-diff] v10-0007-snowflake-Add-sequence-AM-based-on-it.patch (28.0K, ../../[email protected]/8-v10-0007-snowflake-Add-sequence-AM-based-on-it.patch) download | inline diff: From 87b21f2b24e5f123c299a200ad5d2be100c8469a Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 26 Apr 2024 15:09:00 +0900 Subject: [PATCH v10 7/7] snowflake: Add sequence AM based on it This includes documentation and a basic implementation, though this is in a very early stage. --- doc/src/sgml/contrib.sgml | 1 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/snowflake.sgml | 100 ++++ contrib/Makefile | 1 + contrib/meson.build | 1 + contrib/snowflake/.gitignore | 3 + contrib/snowflake/Makefile | 19 + contrib/snowflake/expected/snowflake.out | 73 +++ contrib/snowflake/meson.build | 34 ++ contrib/snowflake/snowflake--1.0.sql | 21 + contrib/snowflake/snowflake.c | 570 +++++++++++++++++++++++ contrib/snowflake/snowflake.control | 5 + contrib/snowflake/sql/snowflake.sql | 29 ++ 13 files changed, 858 insertions(+) create mode 100644 doc/src/sgml/snowflake.sgml create mode 100644 contrib/snowflake/.gitignore create mode 100644 contrib/snowflake/Makefile create mode 100644 contrib/snowflake/expected/snowflake.out create mode 100644 contrib/snowflake/meson.build create mode 100644 contrib/snowflake/snowflake--1.0.sql create mode 100644 contrib/snowflake/snowflake.c create mode 100644 contrib/snowflake/snowflake.control create mode 100644 contrib/snowflake/sql/snowflake.sql diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml index 7c381949a5..7f55133bd2 100644 --- a/doc/src/sgml/contrib.sgml +++ b/doc/src/sgml/contrib.sgml @@ -167,6 +167,7 @@ CREATE EXTENSION <replaceable>extension_name</replaceable>; &seg; &sepgsql; &contrib-spi; + &snowflake; &sslinfo; &tablefunc; &tcn; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 4c367bd5fe..5247bee322 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -157,6 +157,7 @@ <!ENTITY seg SYSTEM "seg.sgml"> <!ENTITY contrib-spi SYSTEM "contrib-spi.sgml"> <!ENTITY sepgsql SYSTEM "sepgsql.sgml"> +<!ENTITY snowflake SYSTEM "snowflake.sgml"> <!ENTITY sslinfo SYSTEM "sslinfo.sgml"> <!ENTITY tablefunc SYSTEM "tablefunc.sgml"> <!ENTITY tcn SYSTEM "tcn.sgml"> diff --git a/doc/src/sgml/snowflake.sgml b/doc/src/sgml/snowflake.sgml new file mode 100644 index 0000000000..060699e7ec --- /dev/null +++ b/doc/src/sgml/snowflake.sgml @@ -0,0 +1,100 @@ +<!-- doc/src/sgml/snowflake.sgml --> + +<sect1 id="snowflake" xreflabel="snowflake"> + <title>snowflake — sequence access method</title> + + <indexterm zone="snowflake"> + <primary>snowflake</primary> + </indexterm> + + <para> + <literal>snowflake</literal> provides a sequence access method based on + <ulink url="https://en.wikipedia.org/wiki/Snowflake_ID">Snowflake IDs</ulink>. + </para> + + <para> + A Snowflake ID (or snowflake) is a unique 64-bit identifier made of three + components: + <itemizedlist spacing="compact"> + <listitem><para>41 bits for a timestamp, epoch-adjusted in milli-seconds</para></listitem> + <listitem><para>10 bits for machine ID</para></listitem> + <listitem><para>12 bits for a sequence number</para></listitem> + </itemizedlist> + </para> + + <sect2 id="snowflake-functions"> + <title>Functions</title> + + <variablelist> + <varlistentry> + <term> + <function>snowflake_get(raw int8) returns record</function> + <indexterm> + <primary>snowflake_get</primary> + <secondary>function</secondary> + </indexterm> + </term> + + <listitem> + <para> + Returns a record made of the timestamp in milli-seconds, the machine ID + and the sequence number for a single snowflake ID. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-parameters"> + <title>Configuration Parameters</title> + + <variablelist> + <varlistentry> + <term> + <varname>snowflake.machine_id</varname> + <indexterm> + <primary><varname>snowflake.machine_id</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Machine ID assigned to the snowflake IDs used in the sequence. The + default value is <literal>1</literal>. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-examples"> + <title>Examples</title> + + <para> + This is an example of creating a snowflake sequence: + </para> + +<programlisting> +CREATE SEQUENCE snowflake_seq USING snowflake; +</programlisting> + + <para> + Similarly to the default sequence access method, snowflake sequences + can be queried as a table: + </para> + +<programlisting> + =# SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) +=# SELECT to_timestamp(time_ms / 1000), machine, counter + FROM snowflake_get(nextval('snowflake_seq')); + to_timestamp | machine | counter +------------------------+---------+--------- + 2024-04-26 14:28:26+09 | 1 | 3 +(1 row) +</programlisting> + </sect2> + +</sect1> diff --git a/contrib/Makefile b/contrib/Makefile index 952855d9b6..26ec317349 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -43,6 +43,7 @@ SUBDIRS = \ pg_walinspect \ postgres_fdw \ seg \ + snowflake \ spi \ tablefunc \ tcn \ diff --git a/contrib/meson.build b/contrib/meson.build index 159ff41555..3652c85ac8 100644 --- a/contrib/meson.build +++ b/contrib/meson.build @@ -58,6 +58,7 @@ subdir('pg_walinspect') subdir('postgres_fdw') subdir('seg') subdir('sepgsql') +subdir('snowflake') subdir('spi') subdir('sslinfo') # start-scripts doesn't contain build products diff --git a/contrib/snowflake/.gitignore b/contrib/snowflake/.gitignore new file mode 100644 index 0000000000..44d119cfcc --- /dev/null +++ b/contrib/snowflake/.gitignore @@ -0,0 +1,3 @@ +# Generated subdirectories +/log/ +/results/ diff --git a/contrib/snowflake/Makefile b/contrib/snowflake/Makefile new file mode 100644 index 0000000000..fa5b48d565 --- /dev/null +++ b/contrib/snowflake/Makefile @@ -0,0 +1,19 @@ +# contrib/snowflake/Makefile + +MODULES = snowflake + +EXTENSION = snowflake +DATA = snowflake--1.0.sql + +REGRESS = snowflake + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/snowflake +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/snowflake/expected/snowflake.out b/contrib/snowflake/expected/snowflake.out new file mode 100644 index 0000000000..b7e469bf73 --- /dev/null +++ b/contrib/snowflake/expected/snowflake.out @@ -0,0 +1,73 @@ +CREATE EXTENSION snowflake; +CREATE SEQUENCE snowflake_seq USING snowflake; +SET snowflake.machine_id = 2000; -- error +ERROR: 2000 is outside the valid range for parameter "snowflake.machine_id" (0 .. 1023) +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(lastval()); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 3 | t +(1 row) + +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + relpersistence +---------------- + u +(1 row) + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; + machine | counter +---------+--------- + 4 | 2 + 4 | 3 + 4 | 4 + 4 | 5 + 4 | 6 + 4 | 7 + 4 | 8 + 4 | 9 + 4 | 10 + 4 | 11 +(10 rows) + +DROP TABLE snowflake_tab; +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; diff --git a/contrib/snowflake/meson.build b/contrib/snowflake/meson.build new file mode 100644 index 0000000000..567669eea7 --- /dev/null +++ b/contrib/snowflake/meson.build @@ -0,0 +1,34 @@ +# Copyright (c) 2024, PostgreSQL Global Development Group + +snowflake_sources = files( + 'snowflake.c', +) + +if host_system == 'windows' + snowflake_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'snowflake', + '--FILEDESC', 'snowflake - sequence access method',]) +endif + +snowflake = shared_module('snowflake', + snowflake_sources, + kwargs: contrib_mod_args, +) +contrib_targets += bloom + +install_data( + 'snowflake.control', + 'snowflake--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'snowflake', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'snowflake', + ], + }, +} diff --git a/contrib/snowflake/snowflake--1.0.sql b/contrib/snowflake/snowflake--1.0.sql new file mode 100644 index 0000000000..bcb9d754f1 --- /dev/null +++ b/contrib/snowflake/snowflake--1.0.sql @@ -0,0 +1,21 @@ +/* contrib/snowflake/snowflake--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION snowflake" to load this file. \quit + +CREATE FUNCTION snowflake_sequenceam_handler(internal) + RETURNS sequence_am_handler + AS 'MODULE_PATHNAME' + LANGUAGE C; + +CREATE ACCESS METHOD snowflake + TYPE SEQUENCE HANDLER snowflake_sequenceam_handler; +COMMENT ON ACCESS METHOD snowflake IS 'snowflake sequence access method'; + +CREATE FUNCTION snowflake_get(IN raw int8, + OUT time_ms int8, + OUT machine int4, + OUT counter int4) + RETURNS record + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT diff --git a/contrib/snowflake/snowflake.c b/contrib/snowflake/snowflake.c new file mode 100644 index 0000000000..be4c4039ec --- /dev/null +++ b/contrib/snowflake/snowflake.c @@ -0,0 +1,570 @@ +/*------------------------------------------------------------------------- + * + * snowflake.c + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * contrib/snowflake/snowflake.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <sys/time.h> + +#include "access/generic_xlog.h" +#include "access/sequenceam.h" +#include "access/xact.h" +#include "catalog/storage_xlog.h" +#include "commands/tablecmds.h" +#include "fmgr.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "storage/bufmgr.h" +#include "utils/guc.h" +#include "utils/numeric.h" + +PG_MODULE_MAGIC; + +/* "special area" of a snowflake's buffer page. */ +#define SNOWFLAKE_MAGIC 0x01 + +typedef struct snowflake_magic +{ + uint32 magic; +} snowflake_magic; + +/* ----------------------------------------------------------------------- + * Snowflake ID are 64-bit based, with the following structure: + * - 41 bits for an epoch-based timestamp, in milli-seconds. + * - 10 bits for a machine ID. + * - 12 bits for a sequence counter. + * + * The timestamp can be cut to an offset. The machine ID is controlled + * by a superuser GUC. Sequence properties apply to the sequence counter, + * as the other two are environment-dependent. + * ----------------------------------------------------------------------- + */ + +/* + * Helper routines to convert a snowflake ID from/to an int64. + */ +#define SNOWFLAKE_COUNTER_MASK 0x0000000000000FFF /* 12 bits */ +#define SNOWFLAKE_COUNTER_SHIFT 0 +#define SNOWFLAKE_MACHINE_ID_MASK 0x00000000000003FF /* 10 bits */ +#define SNOWFLAKE_MACHINE_ID_SHIFT 12 /* counter */ +#define SNOWFLAKE_TIMESTAMP_MASK 0x000001FFFFFFFFFF /* 41 bits */ +#define SNOWFLAKE_TIMESTAMP_SHIFT 22 /* machine ID + counter sizes */ + +typedef struct snowflake_id +{ + uint32 count; + uint32 machine; + uint64 time_ms; +} snowflake_id; + +#define snowflake_id_to_int64(id, raw) { \ + raw = (((id).count) & SNOWFLAKE_COUNTER_MASK) << SNOWFLAKE_COUNTER_SHIFT | \ + (((id).machine) & SNOWFLAKE_MACHINE_ID_MASK) << SNOWFLAKE_MACHINE_ID_SHIFT | \ + (((id).time_ms) & SNOWFLAKE_TIMESTAMP_MASK) << SNOWFLAKE_TIMESTAMP_SHIFT; \ +} + +#define int64_to_snowflake_id(raw, id) { \ + (id).count = ((raw) >> SNOWFLAKE_COUNTER_SHIFT) & SNOWFLAKE_COUNTER_MASK; \ + (id).machine = ((raw) >> SNOWFLAKE_MACHINE_ID_SHIFT) & SNOWFLAKE_MACHINE_ID_MASK; \ + (id).time_ms = ((raw) >> SNOWFLAKE_TIMESTAMP_SHIFT) & SNOWFLAKE_TIMESTAMP_MASK; \ +} + +/* + * Format of tuples stored in heap table associated to snowflake sequence. + */ +typedef struct FormData_snowflake_data +{ + /* enough to cover 12 bits of the internal counter */ + int16 count; + bool is_called; +} FormData_snowflake_data; + +typedef FormData_snowflake_data *Form_snowflake_data; + +/* + * Columns of a snowflake sequence relation. + */ +#define SNOWFLAKE_COL_COUNT 1 +#define SNOWFLAKE_COL_CALLED 2 + +#define SNOWFLAKE_COLS 2 + +/* GUC parameter */ +static int snowflake_machine_id = 1; + +PG_FUNCTION_INFO_V1(snowflake_sequenceam_handler); + +/* ----------------------------------------------------------------------- + * Interfaces for relation manipulation. + * ----------------------------------------------------------------------- + */ + +/* + * Initialize snowflake relation's fork with some data. + */ +static void +fill_snowflake_fork(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + snowflake_magic *sm; + OffsetNumber offnum; + GenericXLogState *state = NULL; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(snowflake_magic)); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + sm->magic = SNOWFLAKE_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= HEAP_XMAX_INVALID; + ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber); + + /* + * Initialize before entering in the critical section, as this does + * allocations. + */ + if (forkNum == INIT_FORKNUM) + state = GenericXLogStart(rel); + + START_CRIT_SECTION(); + + MarkBufferDirty(buf); + + offnum = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* + * Init forks have to be logged. These go through generic WAL records + * for simplicity's sake to save from the need of a custom RMGR. + */ + if (forkNum == INIT_FORKNUM) + { + (void) GenericXLogRegisterBuffer(state, buf, GENERIC_XLOG_FULL_IMAGE); + GenericXLogFinish(state); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * Initialize snowflake relation. + * + * This needs to handle both the initial and main forks. + */ +static void +fill_snowflake(Relation rel, HeapTuple tuple) +{ + SMgrRelation srel; + + Assert(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED); + + fill_snowflake_fork(rel, tuple, MAIN_FORKNUM); + + /* init fork */ + srel = smgropen(rel->rd_locator, INVALID_PROC_NUMBER); + smgrcreate(srel, INIT_FORKNUM, false); + log_smgrcreate(&rel->rd_locator, INIT_FORKNUM); + fill_snowflake_fork(rel, tuple, INIT_FORKNUM); + FlushRelationBuffers(rel); + smgrclose(srel); +} + +/* + * Read the current state of a snowflake sequence + * + * Given an opened sequence relation, lock the page buffer and find the tuple. + * + * *buf receives the reference to the pinned-and-ex-locked buffer. + * *seqdatatuple receives the reference to the sequence tuple proper. + * + * Returns value points to the data payload of the tuple. + */ +static Form_snowflake_data +read_snowflake(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + snowflake_magic *sm; + Form_snowflake_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SNOWFLAKE_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_snowflake_data) GETSTRUCT(seqdatatuple); + + return seq; +} + + +/* ------------------------------------------------------------------------ + * Callbacks for the snowflake 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 * +snowflake_sequenceam_get_table_am(void) +{ + return "heap"; +} + +/* + * snowflake_sequenceam_init + * + * Initialize relation of a snowflake sequence. This stores the sequence + * counter in an unlogged relation as timestamps ensure value unicity. + */ +static void +snowflake_sequenceam_init(Relation rel, int64 last_value, bool is_called) +{ + Datum values[SNOWFLAKE_COLS]; + bool nulls[SNOWFLAKE_COLS]; + int16 counter; + TupleDesc tupdesc; + HeapTuple tuple; + List *elts = NIL; + ListCell *lc; + ColumnDef *coldef = NULL; + AlterTableCmd *atcmd; + List *atcmds = NIL; + + /* Adjust last_value, depending on the defaults given */ + counter = ((int16) last_value) & SNOWFLAKE_COUNTER_MASK; + + /* + * Create unlogged relation with its attributes. + */ + coldef = makeColumnDef("count", INT2OID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + + foreach(lc, elts) + { + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + /* + * Switch the relation to be unlogged. This forces a rewrite, but + * the relation is empty so that's OK. + */ + RelationSetNewRelfilenumber(rel, RELPERSISTENCE_UNLOGGED); + + /* And insert its first tuple */ + values[0] = Int16GetDatum(counter); + nulls[0] = false; + values[1] = BoolGetDatum(is_called); + nulls[1] = false; + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, values, nulls); + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_nextval + * + * Return the next value for a snowflake sequence. + */ +static int64 +snowflake_sequenceam_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + Buffer buf; + Form_snowflake_data seq; + HeapTupleData seqdatatuple; + int64 result = 0; + snowflake_id id = {0}; + struct timeval tp; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* + * The logic here is quite simple, increment the counter until its + * threshold is reached and get back to the start. If the threshold + * is reached, wait 1ms to ensure a unique timestamp. There is no + * need to do a retry as the buffer is already locked. + */ + id.count = seq->count; + id.count++; + + if (id.count > (PG_INT16_MAX & SNOWFLAKE_COUNTER_MASK)) + { + /* + * Threshold reached, so wait a bit for force clock to a new + * timestamp. + */ + id.count = 1; + pg_usleep(1000L); /* 1ms */ + } + + /* Compute timestamp, with buffer locked */ + gettimeofday(&tp, NULL); + id.time_ms = (uint64) tp.tv_sec * 1000 + + tp.tv_usec / 1000; + + /* Machine ID */ + id.machine = snowflake_machine_id; + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->count = id.count; + seq->is_called = true; + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + /* Store the last value computed for lastval() */ + snowflake_id_to_int64(id, result); + *last = result; + return result; +} + +/* + * snowflake_sequenceam_setval + * + * Set the sequence value, manipulating only the sequence counter. + */ +static void +snowflake_sequenceam_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* Change the in-buffer tuple */ + START_CRIT_SECTION(); + seq->count = (next & SNOWFLAKE_COUNTER_MASK); + seq->is_called = iscalled; + MarkBufferDirty(buf); + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_get_state + * + * Return the last sequence counter value. + */ +static void +snowflake_sequenceam_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + seq = read_snowflake(rel, &buf, &seqdatatuple); + *last_value = seq->count; + *is_called = seq->is_called; + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_reset + * + * Reset the sequence, coming down to resetting its counter. + */ +static void +snowflake_sequenceam_reset(Relation rel, int64 startv, bool is_called, + bool reset_state) +{ + HeapTupleData seqdatatuple; + HeapTuple tuple; + Form_snowflake_data seq; + Buffer buf; + + /* lock buffer page and read tuple */ + (void) read_snowflake(rel, &buf, &seqdatatuple); + + /* copy the existing tuple */ + tuple = 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 = (Form_snowflake_data) GETSTRUCT(tuple); + seq->count = (startv & SNOWFLAKE_COUNTER_MASK); + seq->is_called = is_called; + + /* create new storage */ + RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence); + + /* insert the modified tuple into the page */ + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_change_persistence + * + * There is nothing to do here, the underneath relation has to remain + * unlogged and is set as such when creating the sequence. + */ +static void +snowflake_sequenceam_change_persistence(Relation rel, char newrelpersistence) +{ + /* Nothing to do here */ +} + +/* ------------------------------------------------------------------------ + * Definition of the snowflake sequence access method. + * ------------------------------------------------------------------------ + */ + +static const SequenceAmRoutine snowflake_sequenceam_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = snowflake_sequenceam_get_table_am, + .init = snowflake_sequenceam_init, + .nextval = snowflake_sequenceam_nextval, + .setval = snowflake_sequenceam_setval, + .get_state = snowflake_sequenceam_get_state, + .reset = snowflake_sequenceam_reset, + .change_persistence = snowflake_sequenceam_change_persistence +}; + +Datum +snowflake_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&snowflake_sequenceam_methods); +} + +/* Utility functions */ + +/* + * snowflake_get + * + * Return a tuple worth of snowflake ID data, in a readable shape. + */ +PG_FUNCTION_INFO_V1(snowflake_get); +Datum +snowflake_get(PG_FUNCTION_ARGS) +{ +#define SNOWFLAKE_GET_COLS 3 + int64 raw = PG_GETARG_INT64(0); + Datum *values; + bool *nulls; + TupleDesc tupdesc; + snowflake_id id; + + /* determine result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + int64_to_snowflake_id(raw, id); + + nulls = palloc0(sizeof(bool) * tupdesc->natts); + values = palloc0(sizeof(Datum) * tupdesc->natts); + + values[0] = Int64GetDatum(id.time_ms); + values[1] = Int32GetDatum(id.machine); + values[2] = Int32GetDatum(id.count); + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Entry point when loading extension. + */ +void +_PG_init(void) +{ + DefineCustomIntVariable("snowflake.machine_id", + "Machine ID to use with snowflake sequence.", + "Default value is 1.", + &snowflake_machine_id, + 1, 0, 1023, /* 10 bits as max */ + PGC_SUSET, + 0, NULL, NULL, NULL); +} diff --git a/contrib/snowflake/snowflake.control b/contrib/snowflake/snowflake.control new file mode 100644 index 0000000000..7b8c6089c2 --- /dev/null +++ b/contrib/snowflake/snowflake.control @@ -0,0 +1,5 @@ +# snowflake extension +comment = 'snowflake - sequence access method' +default_version = '1.0' +module_pathname = '$libdir/snowflake' +relocatable = true diff --git a/contrib/snowflake/sql/snowflake.sql b/contrib/snowflake/sql/snowflake.sql new file mode 100644 index 0000000000..395d166ba4 --- /dev/null +++ b/contrib/snowflake/sql/snowflake.sql @@ -0,0 +1,29 @@ +CREATE EXTENSION snowflake; + +CREATE SEQUENCE snowflake_seq USING snowflake; + +SET snowflake.machine_id = 2000; -- error +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(lastval()); +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; +DROP TABLE snowflake_tab; + +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; -- 2.45.2 [application/pgp-signature] signature.asc (833B, ../../[email protected]/9-signature.asc) download ^ permalink raw reply [nested|flat] 14+ messages in thread
* Re: Sequence Access Methods, round two @ 2025-02-18 05:11 Michael Paquier <[email protected]> parent: Michael Paquier <[email protected]> 0 siblings, 1 reply; 14+ messages in thread From: Michael Paquier @ 2025-02-18 05:11 UTC (permalink / raw) To: Kirill Reshke <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Peter Smith <[email protected]>; Postgres hackers <[email protected]> On Wed, Dec 04, 2024 at 08:36:56AM +0900, Michael Paquier wrote: > Indeed. There were a couple of blips in the backend and the dump > parts of the patch set. Rebased as v10 attached. Conflicts caused by ed5e5f071033 in the third patch in sequence.c with adjustments in the new files seqlocalam.c were required, hence here is a rebased version labelled v11. -- Michael Attachments: [text/x-diff] v11-0001-Remove-FormData_pg_sequence_data-from-init_param.patch (9.3K, ../../[email protected]/2-v11-0001-Remove-FormData_pg_sequence_data-from-init_param.patch) download | inline diff: From 934fba0f3f63d8aa49e3e77fe452f9f51dac4fc4 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:00:45 +0900 Subject: [PATCH v11 1/7] 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/sequence.c index 4b7c5113aabe..576c2ec502db 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 rel, 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); 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 = makeNode(CreateStmt); @@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) /* 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); /* @@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) { case SEQ_COL_LASTVAL: coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(seqdataform.last_value); + value[i - 1] = Int64GetDatumFast(last_value); break; case SEQ_COL_LOG: coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); @@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) ObjectAddress address; Relation rel; HeapTuple seqtuple; + bool reset_state = false; + bool is_called; + int64 last_value; HeapTuple newdatatuple; /* 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 = heap_copytuple(&datatuple); newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); + last_value = newdataform->last_value; + is_called = newdataform->is_called; UnlockReleaseBuffer(buf); /* 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); /* 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 = last_value; + newdataform->is_called = is_called; + if (reset_state) + newdataform->log_cnt = 0; fill_seq_with_data(seqrel, newdatatuple); } @@ -1236,17 +1249,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). Also + * 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 overridden. * * Note: we force a sequence rewrite whenever we change parameters that affect - * 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. Currently, * 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 @@ -1257,7 +1272,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) { @@ -1363,11 +1380,11 @@ init_params(ParseState *pstate, List *options, bool for_identity, } /* - * 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 = 0; + *reset_state = true; /* AS type */ if (as_type != NULL) @@ -1416,7 +1433,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("INCREMENT must not be zero"))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1428,7 +1445,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, { seqform->seqcycle = boolVal(is_cycled->arg); Assert(BoolIsValid(seqform->seqcycle)); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1439,7 +1456,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (max_value != NULL && max_value->arg) { seqform->seqmax = defGetInt64(max_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || max_value != NULL || reset_max_value) { @@ -1455,7 +1472,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmax = -1; /* descending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate maximum value. No need to check INT8 as seqmax is an int64 */ @@ -1471,7 +1488,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (min_value != NULL && min_value->arg) { seqform->seqmin = defGetInt64(min_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || min_value != NULL || reset_min_value) { @@ -1487,7 +1504,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmin = 1; /* ascending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate minimum value. No need to check INT8 as seqmin is an int64 */ @@ -1538,30 +1555,30 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (restart_value != NULL) { if (restart_value->arg != NULL) - seqdataform->last_value = defGetInt64(restart_value); + *last_value = defGetInt64(restart_value); else - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; - seqdataform->log_cnt = 0; + *last_value = seqform->seqstart; + *is_called = false; + *reset_state = true; } else if (isInit) { - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; + *last_value = seqform->seqstart; + *is_called = false; } /* 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))); /* CACHE */ @@ -1573,7 +1590,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("CACHE (%lld) must be greater than zero", (long long) seqform->seqcache))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { -- 2.47.2 [text/x-diff] v11-0002-Integrate-addition-of-attributes-for-sequences-w.patch (11.2K, ../../[email protected]/3-v11-0002-Integrate-addition-of-attributes-for-sequences-w.patch) download | inline diff: From c130d7d724f597b31ba6b7e04cb089fa5cb0bcc8 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 19 Jan 2024 15:00:14 +0900 Subject: [PATCH v11 2/7] 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 8dd421fa0ef3..30cd7188c5c1 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2402,6 +2402,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/sequence.c index 576c2ec502db..a902c6ce6f09 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 = NIL; + List *atcmds = 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 = NIL; for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) { ColumnDef *coldef = NULL; @@ -198,7 +200,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) coldef->is_not_null = true; null[i - 1] = false; - stmt->tableElts = lappend(stmt->tableElts, coldef); + elts = lappend(elts, coldef); } stmt->relation = seq->sequence; @@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; + /* + * Initial relation has no attributes, these are added later. + */ + stmt->tableElts = NIL; address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + tupDesc = RelationGetDescr(rel); /* now initialize the sequence's data */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 72a1b64c2a22..e466508c1d3b 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4619,6 +4619,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 */ @@ -4914,6 +4915,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* Recursion occurs during execution phase */ pass = 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 = 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, @@ -5344,6 +5352,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 = ATExecAddColumn(wqueue, tab, rel, &cmd, cmd->recurse, false, @@ -6501,6 +6510,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 25fe3d580166..0629dc5387c0 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1667,7 +1667,11 @@ ProcessUtilitySlow(ParseState *pstate, break; case T_CreateSeqStmt: + EventTriggerAlterTableStart(parsetree); address = DefineSequence(pstate, (CreateSeqStmt *) parsetree); + /* stashed internally */ + commandCollected = true; + EventTriggerAlterTableEnd(); break; case T_AlterSeqStmt: diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/test/modules/test_ddl_deparse/expected/alter_table.out index 50d0354a3417..ed31059ef584 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 of sequence parent_b_seq NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type ADD COLUMN (and recurse) desc column b of table parent NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint parent_b_not_null on table parent @@ -70,7 +73,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 parent_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 of 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 table parent diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out index 5837ea484e40..310ce5a6baf5 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 of 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 14915f661a89..527c67995a94 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 of 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 of 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 of sequence datatype_table_is_small_seq NOTICE: DDL test: type simple, tag CREATE TABLE NOTICE: DDL test: type simple, tag CREATE INDEX NOTICE: DDL test: type simple, tag CREATE INDEX diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c index 193669f2bc1e..254fdf90c79a 100644 --- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c +++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c @@ -113,6 +113,9 @@ get_altertable_subcmdinfo(PG_FUNCTION_ARGS) case AT_AddColumn: strtype = "ADD COLUMN"; break; + case AT_AddColumnToSequence: + strtype = "ADD COLUMN TO SEQUENCE"; + break; case AT_AddColumnToView: strtype = "ADD COLUMN TO VIEW"; break; -- 2.47.2 [text/x-diff] v11-0003-Refactor-code-for-in-core-local-sequences.patch (54.5K, ../../[email protected]/4-v11-0003-Refactor-code-for-in-core-local-sequences.patch) download | inline diff: From a76e0133e5d177d9ebe9c08b34aaf6f18191d82c Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Thu, 25 Apr 2024 16:49:47 +0900 Subject: [PATCH v11 3/7] Refactor code for in-core "local" sequences This commit restructures the code of in-core sequences into a new set of files: - seqdesc.c is renamed to seqlocaldesc.c. - seqlocalxlog.c to the code in the WAL replay logic. - seqlocalam.c to store a set of routines called from sequence.c, finishing the separation between the main sequence logic and the in-core sequences. - seqlocalam.h to store the AM-specific structures and routine related to the in-core sequences. WAL records are renamed to "SequenceLocal" with structures, variables and file structures mapping to that. --- src/include/access/rmgrlist.h | 2 +- src/include/access/seqlocalam.h | 58 ++ src/backend/access/rmgrdesc/Makefile | 2 +- src/backend/access/rmgrdesc/meson.build | 2 +- .../rmgrdesc/{seqdesc.c => seqlocaldesc.c} | 20 +- src/backend/access/sequence/Makefile | 2 +- src/backend/access/sequence/meson.build | 2 + src/backend/access/sequence/seqlocalam.c | 634 ++++++++++++++++++ src/backend/access/sequence/seqlocalxlog.c | 82 +++ src/backend/access/transam/rmgr.c | 1 + src/backend/commands/sequence.c | 634 +----------------- src/bin/pg_waldump/.gitignore | 2 +- src/bin/pg_waldump/rmgrdesc.c | 1 + src/bin/pg_waldump/t/001_basic.pl | 2 +- 14 files changed, 822 insertions(+), 622 deletions(-) create mode 100644 src/include/access/seqlocalam.h rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%) create mode 100644 src/backend/access/sequence/seqlocalam.c create mode 100644 src/backend/access/sequence/seqlocalxlog.c diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h index 8e7fc9db8778..b942b25350bc 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, btree_identify, btree_xlog PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL, hash_mask, NULL) PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, 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, NULL, seq_mask, NULL) +PG_RMGR(RM_SEQ_LOCAL_ID, "SequenceLocal", seq_local_redo, seq_local_desc, seq_local_identify, NULL, NULL, seq_local_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, NULL, brin_mask, NULL) PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL, NULL, NULL) diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h new file mode 100644 index 000000000000..225fb9a2cbeb --- /dev/null +++ b/src/include/access/seqlocalam.h @@ -0,0 +1,58 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.h + * Local sequence access method. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/access/seqlocalam.h + * + *------------------------------------------------------------------------- + */ +#ifndef SEQLOCALAM_H +#define SEQLOCALAM_H + +#include "access/xlogreader.h" +#include "storage/relfilelocator.h" +#include "utils/rel.h" + +/* XLOG stuff */ +#define XLOG_SEQ_LOCAL_LOG 0x00 + +typedef struct xl_seq_local_rec +{ + RelFileLocator locator; + /* SEQUENCE TUPLE DATA FOLLOWS AT THE END */ +} xl_seq_local_rec; + +/* + * The "special area" of a local sequence's buffer page looks like this. + */ +#define SEQ_LOCAL_MAGIC 0x1717 + +typedef struct seq_local_magic +{ + uint32 magic; +} seq_local_magic; + +extern void seq_local_redo(XLogReaderState *record); +extern void seq_local_desc(StringInfo buf, XLogReaderState *record); +extern const char *seq_local_identify(uint8 info); +extern void seq_local_mask(char *page, BlockNumber blkno); + +/* access routines */ +extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last); +extern const char *seq_local_get_table_am(void); +extern void seq_local_init(Relation rel, int64 last_value, bool is_called); +extern void seq_local_setval(Relation rel, int64 next, bool iscalled); +extern void seq_local_reset(Relation rel, int64 startv, bool is_called, + bool reset_state); +extern void seq_local_get_state(Relation rel, int64 *last_value, + bool *is_called); +extern void seq_local_change_persistence(Relation rel, + char newrelpersistence); + +#endif /* SEQLOCALAM_H */ diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile index cd95eec37f14..e5900ed77af5 100644 --- a/src/backend/access/rmgrdesc/Makefile +++ b/src/backend/access/rmgrdesc/Makefile @@ -24,7 +24,7 @@ OBJS = \ relmapdesc.o \ replorigindesc.o \ rmgrdesc_utils.o \ - seqdesc.o \ + seqlocaldesc.o \ smgrdesc.o \ spgdesc.o \ standbydesc.o \ diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build index 96c98e800c22..6b3b3e1e44c1 100644 --- a/src/backend/access/rmgrdesc/meson.build +++ b/src/backend/access/rmgrdesc/meson.build @@ -17,7 +17,7 @@ rmgr_desc_sources = files( 'relmapdesc.c', 'replorigindesc.c', 'rmgrdesc_utils.c', - 'seqdesc.c', + 'seqlocaldesc.c', 'smgrdesc.c', 'spgdesc.c', 'standbydesc.c', diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/seqlocaldesc.c similarity index 64% rename from src/backend/access/rmgrdesc/seqdesc.c rename to src/backend/access/rmgrdesc/seqlocaldesc.c index 0d289d77fcf7..6ffbcb2c4735 100644 --- a/src/backend/access/rmgrdesc/seqdesc.c +++ b/src/backend/access/rmgrdesc/seqlocaldesc.c @@ -1,44 +1,44 @@ /*------------------------------------------------------------------------- * - * seqdesc.c - * rmgr descriptor routines for commands/sequence.c + * seqlocaldesc.c + * rmgr descriptor routines for sequence/seqlocal.c * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * src/backend/access/rmgrdesc/seqdesc.c + * src/backend/access/rmgrdesc/seqlocaldesc.c * *------------------------------------------------------------------------- */ #include "postgres.h" -#include "commands/sequence.h" +#include "access/seqlocalam.h" void -seq_desc(StringInfo buf, XLogReaderState *record) +seq_local_desc(StringInfo buf, XLogReaderState *record) { char *rec = XLogRecGetData(record); uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - xl_seq_rec *xlrec = (xl_seq_rec *) rec; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) rec; - if (info == XLOG_SEQ_LOG) + if (info == XLOG_SEQ_LOCAL_LOG) appendStringInfo(buf, "rel %u/%u/%u", xlrec->locator.spcOid, xlrec->locator.dbOid, xlrec->locator.relNumber); } const char * -seq_identify(uint8 info) +seq_local_identify(uint8 info) { const char *id = NULL; switch (info & ~XLR_INFO_MASK) { - case XLOG_SEQ_LOG: - id = "LOG"; + case XLOG_SEQ_LOCAL_LOG: + id = "SEQ_LOCAL_LOG"; break; } diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile index 9f9d31f5425a..a15ceec1c0a0 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index ec9ab9b7e9db..8bc0e95e68c0 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -1,5 +1,7 @@ # Copyright (c) 2022-2025, PostgreSQL Global Development Group backend_sources += files( + 'seqlocalam.c', + 'seqlocalxlog.c', 'sequence.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c new file mode 100644 index 000000000000..bc735ebb0838 --- /dev/null +++ b/src/backend/access/sequence/seqlocalam.c @@ -0,0 +1,634 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.c + * Local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalam.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/multixact.h" +#include "access/seqlocalam.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 SEQ_LOCAL_LOG_VALS 32 + +/* Format of tuples stored in heap table associated to local sequences */ +typedef struct FormData_pg_seq_local_data +{ + int64 last_value; + int64 log_cnt; + bool is_called; +} FormData_pg_seq_local_data; + +typedef FormData_pg_seq_local_data *Form_pg_seq_local_data; + +/* + * Columns of a local sequence relation + */ +#define SEQ_LOCAL_COL_LASTVAL 1 +#define SEQ_LOCAL_COL_LOG 2 +#define SEQ_LOCAL_COL_CALLED 3 + +#define SEQ_LOCAL_COL_FIRSTCOL SEQ_LOCAL_COL_LASTVAL +#define SEQ_LOCAL_COL_LASTCOL SEQ_LOCAL_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_LOCAL_LOG_VALS 32 + +static Form_pg_seq_local_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 tuple + * + * *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_seq_local_data +read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + seq_local_magic *sm; + Form_pg_seq_local_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SEQ_LOCAL_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_pg_seq_local_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 == RELPERSISTENCE_UNLOGGED) + { + SMgrRelation srel; + + srel = 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 content + */ +static void +fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + seq_local_magic *sm; + OffsetNumber offnum; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + sm->magic = SEQ_LOCAL_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + + XLogRegisterData(&xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData(tuple->t_data, tuple->t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_nextval() + * + * Allocate a new value for a local sequence, based on the sequence + * configuration. + */ +int64 +seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + int64 result; + int64 fetch; + int64 next; + int64 rescnt = 0; + int64 log; + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + Page page; + bool logit = false; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + page = BufferGetPage(buf); + + *last = next = result = seq->last_value; + fetch = cache; + log = 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_LOCAL_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 = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = true; + } + else + { + XLogRecPtr redoptr = GetRedoRecPtr(); + + if (PageGetLSN(page) <= redoptr) + { + /* last update of seq was before checkpoint */ + fetch = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = 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 >= 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 = minv; + } + else + next += incby; + } + else + { + /* descending sequence */ + if ((minv < 0 && next < minv - incby) || + (minv >= 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 = maxv; + } + else + next += incby; + } + fetch--; + if (rescnt < cache) + { + log--; + rescnt++; + *last = next; + if (rescnt == 1) /* if it's first result - */ + result = next; /* it's what to return */ + } + } + + log -= fetch; /* adjust for any unfetched numbers */ + Assert(log >= 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_seq_local_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 = next; + seq->is_called = true; + seq->log_cnt = 0; + + xlrec.locator = rel->rd_locator; + + XLogRegisterData(&xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData(seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + /* Now update sequence tuple to the intended final state */ + seq->last_value = *last; /* last fetched number */ + seq->is_called = true; + seq->log_cnt = log; /* how much is logged */ + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + return result; +} + +/* + * seq_local_get_table_am() + * + * Return the table access method used by this sequence. + */ +const char * +seq_local_get_table_am(void) +{ + return "heap"; +} + +/* + * seq_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 + * the information guessed from pg_sequences. This is the first tuple + * inserted after the relation has been created, filling in its heap + * table. + */ +void +seq_local_init(Relation rel, int64 last_value, bool is_called) +{ + Datum value[SEQ_LOCAL_COL_LASTCOL]; + bool null[SEQ_LOCAL_COL_LASTCOL]; + List *elts = NIL; + List *atcmds = NIL; + ListCell *lc; + TupleDesc tupdesc; + HeapTuple tuple; + + /* + * Create relation (and fill value[] and null[] for the initial tuple). + */ + for (int i = SEQ_LOCAL_COL_FIRSTCOL; i <= SEQ_LOCAL_COL_LASTCOL; i++) + { + ColumnDef *coldef = NULL; + + switch (i) + { + case SEQ_LOCAL_COL_LASTVAL: + coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatumFast(last_value); + break; + case SEQ_LOCAL_COL_LOG: + coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatum(0); + break; + case SEQ_LOCAL_COL_CALLED: + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + value[i - 1] = BoolGetDatum(is_called); + break; + } + + coldef->is_not_null = true; + null[i - 1] = false; + elts = lappend(elts, coldef); + } + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, value, null); + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_setval() + * + * Callback for setval(). + */ +void +seq_local_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->last_value = next; /* last fetched number */ + seq->is_called = iscalled; + seq->log_cnt = 0; + + MarkBufferDirty(buf); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel)) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + Page page = BufferGetPage(buf); + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + XLogRegisterData(&xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData(seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_reset() + * + * Perform a hard reset on the local sequence, rewriting its heap data + * entirely. + */ +void +seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) +{ + Form_pg_seq_local_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 = 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 = (Form_pg_seq_local_data) GETSTRUCT(tuple); + seq->last_value = startv; + seq->is_called = is_called; + if (reset_state) + seq->log_cnt = 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 == InvalidTransactionId); + Assert(rel->rd_rel->relminmxid == InvalidMultiXactId); + + /* + * Insert the modified tuple into the new storage file. + */ + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_get_state() + * + * Retrieve the state of a local sequence. + */ +void +seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + *last_value = seq->last_value; + *is_called = seq->is_called; + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_change_persistence() + * + * Persistence change for the local sequence Relation. + */ +void +seq_local_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/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c new file mode 100644 index 000000000000..db0ad969dbf2 --- /dev/null +++ b/src/backend/access/sequence/seqlocalxlog.c @@ -0,0 +1,82 @@ +/*------------------------------------------------------------------------- + * + * seqlocalxlog.c + * WAL replay logic for local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalxlog.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/bufmask.h" +#include "access/seqlocalam.h" +#include "access/xlogutils.h" +#include "storage/block.h" + +/* + * Mask a Sequence page before performing consistency checks on it. + */ +void +seq_local_mask(char *page, BlockNumber blkno) +{ + mask_page_lsn_and_checksum(page); + + mask_unused_space(page); +} + +void +seq_local_redo(XLogReaderState *record) +{ + XLogRecPtr lsn = record->EndRecPtr; + uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; + Buffer buffer; + Page page; + Page localpage; + char *item; + Size itemsz; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record); + seq_local_magic *sm; + + if (info != XLOG_SEQ_LOCAL_LOG) + elog(PANIC, "seq_redo: unknown op code %u", info); + + buffer = XLogInitBufferForRedo(record, 0); + page = (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 = (Page) palloc(BufferGetPageSize(buffer)); + + PageInit(localpage, BufferGetPageSize(buffer), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(localpage); + sm->magic = SEQ_LOCAL_MAGIC; + + item = (char *) xlrec + sizeof(xl_seq_local_rec); + itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec); + + if (PageAddItem(localpage, (Item) item, itemsz, + FirstOffsetNumber, false, false) == InvalidOffsetNumber) + elog(PANIC, "seq_local_redo: failed to add item to page"); + + PageSetLSN(localpage, lsn); + + memcpy(page, localpage, BufferGetPageSize(buffer)); + MarkBufferDirty(buffer); + UnlockReleaseBuffer(buffer); + + pfree(localpage); +} diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c index 1b7499726eb0..cc92268937b1 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/seqlocalam.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/sequence.c index a902c6ce6f09..b7ed47d92801 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,6 +16,7 @@ #include "access/bufmask.h" #include "access/htup_details.h" +#include "access/seqlocalam.h" #include "access/multixact.h" #include "access/relation.h" #include "access/sequence.h" @@ -50,23 +51,6 @@ #include "utils/varlena.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 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 current * session. This is needed to hold onto nextval/currval state. (We can't @@ -96,13 +80,9 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */ */ static SeqTableData *last_used_seq = NULL; -static void fill_seq_with_data(Relation rel, HeapTuple tuple); -static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber 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_identity, 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 = NIL; - List *atcmds = NIL; - ListCell *lc; Datum pgs_values[Natts_pg_sequence]; bool pgs_nulls[Natts_pg_sequence]; - int i; /* * 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); - /* - * Create relation (and fill value[] and null[] for the tuple) - */ - for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) - { - ColumnDef *coldef = NULL; - - switch (i) - { - case SEQ_COL_LASTVAL: - coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(last_value); - break; - case SEQ_COL_LOG: - coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatum((int64) 0); - break; - case SEQ_COL_CALLED: - coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); - value[i - 1] = BoolGetDatum(false); - break; - } - - coldef->is_not_null = true; - null[i - 1] = false; - - elts = lappend(elts, coldef); - } - stmt->relation = seq->sequence; stmt->inhRelations = NIL; stmt->constraints = NIL; @@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) */ stmt->tableElts = NIL; + /* + * Initial relation has no attributes, these can be added later via the + * "init" AM callback. + */ + stmt->tableElts = NIL; + address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); - /* Add all the attributes to the sequence */ - foreach(lc, elts) - { - AlterTableCmd *atcmd; - - atcmd = makeNode(AlterTableCmd); - atcmd->subtype = AT_AddColumnToSequence; - atcmd->def = (Node *) lfirst(lc); - atcmds = lappend(atcmds, atcmd); - } - - /* - * No recursion needed. Note that EventTriggerAlterTableStart() should - * have been called. - */ - AlterTableInternal(RelationGetRelid(rel), atcmds, false); - CommandCounterIncrement(); - - tupDesc = RelationGetDescr(rel); - - /* now initialize the sequence's data */ - tuple = heap_form_tuple(tupDesc, value, null); - fill_seq_with_data(rel, tuple); + /* now initialize the sequence table structure and its data */ + seq_local_init(rel, last_value, is_called); /* 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); pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid)); if (!HeapTupleIsValid(pgstuple)) @@ -315,40 +240,8 @@ ResetSequence(Oid seq_relid) startv = pgsform->seqstart; ReleaseSysCache(pgstuple); - /* - * Copy the existing sequence tuple. - */ - tuple = 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 = (Form_pg_sequence_data) GETSTRUCT(tuple); - seq->last_value = startv; - seq->is_called = false; - seq->log_cnt = 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 == InvalidTransactionId); - Assert(seq_rel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - fill_seq_with_data(seq_rel, tuple); + /* Sequence state is forcibly reset here. */ + seq_local_reset(seq_rel, startv, false, true); /* 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); } -/* - * 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 == RELPERSISTENCE_UNLOGGED) - { - SMgrRelation srel; - - srel = 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 content - */ -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 = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, - EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); - Assert(BufferGetBlockNumber(buf) == 0); - - page = BufferGetPage(buf); - - PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(page); - sm->magic = SEQ_MAGIC; - - /* Now insert sequence tuple */ - - /* - * Since VACUUM does not process sequences, we have to force the tuple to - * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, - InvalidOffsetNumber, false, false); - if (offnum != FirstOffsetNumber) - elog(ERROR, "failed to add sequence tuple to page"); - - /* XLOG stuff */ - if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = rel->rd_locator; - - XLogRegisterData(&xlrec, sizeof(xl_seq_rec)); - XLogRegisterData(tuple->t_data, tuple->t_len); - - recptr = 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 = false; bool is_called; int64 last_value; - HeapTuple newdatatuple; /* Open and lock sequence, and check for ownership along the way. */ relid = RangeVarGetRelidExtended(stmt->sequence, @@ -507,16 +296,8 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) seqform = (Form_pg_sequence) GETSTRUCT(seqtuple); - /* 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 = heap_copytuple(&datatuple); - newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); - last_value = newdataform->last_value; - is_called = newdataform->is_called; - - UnlockReleaseBuffer(buf); + /* Read sequence data */ + seq_local_get_state(seqrel, &last_value, &is_called); /* 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(); - /* - * 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 == InvalidTransactionId); - Assert(seqrel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - newdataform->last_value = last_value; - newdataform->is_called = is_called; - if (reset_state) - newdataform->log_cnt = 0; - fill_seq_with_data(seqrel, newdatatuple); + seq_local_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) { SeqTable elm; Relation seqrel; - Buffer buf; - HeapTupleData seqdatatuple; /* * ALTER SEQUENCE acquires this lock earlier. If we're processing an @@ -596,10 +353,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - (void) read_seq_tuple(seqrel, &buf, &seqdatatuple); - RelationSetNewRelfilenumber(seqrel, newrelpersistence); - fill_seq_with_data(seqrel, &seqdatatuple); - UnlockReleaseBuffer(buf); + seq_local_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -662,24 +416,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 = 0; + int64 result; bool cycle; - bool logit = false; /* open and lock sequence */ init_sequence(relid, &elm, &seqrel); @@ -724,105 +469,9 @@ nextval_internal(Oid relid, bool check_permissions) cycle = pgsform->seqcycle; ReleaseSysCache(pgstuple); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - page = BufferGetPage(buf); - - last = next = result = seq->last_value; - fetch = cache; - log = 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 = log = fetch + SEQ_LOG_VALS; - logit = true; - } - else - { - XLogRecPtr redoptr = GetRedoRecPtr(); - - if (PageGetLSN(page) <= redoptr) - { - /* last update of seq was before checkpoint */ - fetch = log = fetch + SEQ_LOG_VALS; - logit = 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 >= 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 = minv; - } - else - next += incby; - } - else - { - /* descending sequence */ - if ((minv < 0 && next < minv - incby) || - (minv >= 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 = maxv; - } - else - next += incby; - } - fetch--; - if (rescnt < cache) - { - log--; - rescnt++; - last = next; - if (rescnt == 1) /* if it's first result - */ - result = next; /* it's what to return */ - } - } - - log -= fetch; /* adjust for any unfetched numbers */ - Assert(log >= 0); + /* retrieve next value from the access method */ + result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -832,69 +481,6 @@ nextval_internal(Oid relid, bool check_permissions) last_used_seq = elm; - /* - * 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 = next; - seq->is_called = true; - seq->log_cnt = 0; - - xlrec.locator = seqrel->rd_locator; - - XLogRegisterData(&xlrec, sizeof(xl_seq_rec)); - XLogRegisterData(seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - /* Now update sequence tuple to the intended final state */ - seq->last_value = last; /* last fetched number */ - seq->is_called = true; - seq->log_cnt = log; /* how much is logged */ - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); - sequence_close(seqrel, NoLock); return result; @@ -984,9 +570,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, @@ -1020,9 +603,6 @@ do_setval(Oid relid, int64 next, bool iscalled) */ PreventCommandIfParallelMode("setval()"); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - if ((next < minv) || (next > maxv)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), @@ -1044,37 +624,8 @@ do_setval(Oid relid, int64 next, bool iscalled) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - /* ready to change the on-disk (or really, in-buffer) tuple */ - START_CRIT_SECTION(); - - seq->last_value = next; /* last fetched number */ - seq->is_called = iscalled; - seq->log_cnt = 0; - - MarkBufferDirty(buf); - - /* XLOG stuff */ - if (RelationNeedsWAL(seqrel)) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - Page page = BufferGetPage(buf); - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = seqrel->rd_locator; - XLogRegisterData(&xlrec, sizeof(xl_seq_rec)); - XLogRegisterData(seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); + /* Call the access method callback */ + seq_local_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1215,62 +766,6 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) } -/* - * Given an opened sequence relation, lock the page buffer and find the tuple - * - * *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 = ReadBuffer(rel, 0); - LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); - - page = BufferGetPage(*buf); - sm = (sequence_magic *) PageGetSpecialPointer(page); - - if (sm->magic != SEQ_MAGIC) - elog(ERROR, "bad magic number in sequence \"%s\": %08X", - RelationGetRelationName(rel), sm->magic); - - lp = PageGetItemId(page, FirstOffsetNumber); - Assert(ItemIdIsNormal(lp)); - - /* Note we currently only bother to set these two fields of *seqdatatuple */ - seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); - seqdatatuple->t_len = 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) != InvalidTransactionId) - { - HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); - seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; - seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; - MarkBufferDirtyHint(*buf, true); - } - - seq = (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 @@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; + bool is_called; + int64 last_value; - seq = read_seq_tuple(seqrel, &buf, &seqtuple); + seq_local_get_state(seqrel, &last_value, &is_called); - values[0] = Int64GetDatum(seq->last_value); - values[1] = BoolGetDatum(seq->is_called); - - UnlockReleaseBuffer(buf); + values[0] = Int64GetDatum(last_value); + values[1] = BoolGetDatum(is_called); } else memset(isnull, true, sizeof(isnull)); @@ -1908,17 +1400,9 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; - - seq = read_seq_tuple(seqrel, &buf, &seqtuple); - - is_called = seq->is_called; - result = seq->last_value; - - UnlockReleaseBuffer(buf); + seq_local_get_state(seqrel, &result, &is_called); } + sequence_close(seqrel, NoLock); if (is_called) @@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - -void -seq_redo(XLogReaderState *record) -{ - XLogRecPtr lsn = record->EndRecPtr; - uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - Buffer buffer; - Page page; - Page localpage; - char *item; - Size itemsz; - xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record); - sequence_magic *sm; - - if (info != XLOG_SEQ_LOG) - elog(PANIC, "seq_redo: unknown op code %u", info); - - buffer = XLogInitBufferForRedo(record, 0); - page = (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 = (Page) palloc(BufferGetPageSize(buffer)); - - PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(localpage); - sm->magic = SEQ_MAGIC; - - item = (char *) xlrec + sizeof(xl_seq_rec); - itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec); - - if (PageAddItem(localpage, (Item) item, itemsz, - FirstOffsetNumber, false, false) == 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. */ @@ -1992,14 +1425,3 @@ ResetSequenceCaches(void) last_used_seq = 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 ec51f41c767e..8d1195de2637 100644 --- a/src/bin/pg_waldump/.gitignore +++ b/src/bin/pg_waldump/.gitignore @@ -10,13 +10,13 @@ /gistdesc.c /hashdesc.c /heapdesc.c +/seqlocaldesc.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 6b8c17bb4c4e..2fccc7a4e2bd 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/seqlocalam.h" #include "access/multixact.h" #include "access/nbtxlog.h" #include "access/rmgr.h" diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl index 5c8fea275bb5..56a6e93a9a77 100644 --- a/src/bin/pg_waldump/t/001_basic.pl +++ b/src/bin/pg_waldump/t/001_basic.pl @@ -67,7 +67,7 @@ Btree Hash Gin Gist -Sequence +SequenceLocal SPGist BRIN CommitTs -- 2.47.2 [text/x-diff] v11-0004-Sequence-access-methods-backend-support.patch (64.1K, ../../[email protected]/5-v11-0004-Sequence-access-methods-backend-support.patch) download | inline diff: From f35a5db2325dcbc3b524c3af371e2eb49a60f469 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Wed, 4 Dec 2024 08:09:46 +0900 Subject: [PATCH v11 4/7] Sequence access methods - backend support The "seqlocal" sequence AM is now plugged in as a handler in the relcache, and a set of callbacks in sequenceam.h. --- src/include/access/seqlocalam.h | 15 -- src/include/access/sequenceam.h | 181 ++++++++++++++++++ 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/meson.build | 1 + src/backend/access/sequence/seqlocalam.c | 41 +++- 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 | 23 +-- 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/psql/describe.c | 2 + src/bin/psql/tab-complete.in.c | 4 +- src/test/regress/expected/create_am.out | 55 ++++-- src/test/regress/expected/opr_sanity.out | 12 ++ src/test/regress/expected/psql.out | 96 +++++----- src/test/regress/expected/type_sanity.out | 12 +- src/test/regress/sql/create_am.sql | 24 ++- src/test/regress/sql/opr_sanity.sql | 10 + src/test/regress/sql/type_sanity.sql | 12 +- src/tools/pgindent/typedefs.list | 5 +- 40 files changed, 695 insertions(+), 182 deletions(-) create mode 100644 src/include/access/sequenceam.h create mode 100644 src/backend/access/sequence/sequenceamapi.c diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h index 225fb9a2cbeb..21936511ac2b 100644 --- a/src/include/access/seqlocalam.h +++ b/src/include/access/seqlocalam.h @@ -15,7 +15,6 @@ #include "access/xlogreader.h" #include "storage/relfilelocator.h" -#include "utils/rel.h" /* XLOG stuff */ #define XLOG_SEQ_LOCAL_LOG 0x00 @@ -41,18 +40,4 @@ extern void seq_local_desc(StringInfo buf, XLogReaderState *record); extern const char *seq_local_identify(uint8 info); extern void seq_local_mask(char *page, BlockNumber blkno); -/* access routines */ -extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last); -extern const char *seq_local_get_table_am(void); -extern void seq_local_init(Relation rel, int64 last_value, bool is_called); -extern void seq_local_setval(Relation rel, int64 next, bool iscalled); -extern void seq_local_reset(Relation rel, int64 startv, bool is_called, - bool reset_state); -extern void seq_local_get_state(Relation rel, int64 *last_value, - bool *is_called); -extern void seq_local_change_persistence(Relation rel, - char newrelpersistence); - #endif /* SEQLOCALAM_H */ diff --git a/src/include/access/sequenceam.h b/src/include/access/sequenceam.h new file mode 100644 index 000000000000..ac48c8b468be --- /dev/null +++ b/src/include/access/sequenceam.h @@ -0,0 +1,181 @@ +/*------------------------------------------------------------------------- + * + * sequenceam.h + * POSTGRES sequence access method definitions. + * + * + * Portions Copyright (c) 1996-2024, 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 "seqlocal" + +/* 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 value + * allocated. The result returned is the next value of the sequence computed. + */ +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); + +#endif /* SEQUENCEAM_H */ diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat index 26d15928a155..8f076fcec958 100644 --- a/src/include/catalog/pg_am.dat +++ b/src/include/catalog/pg_am.dat @@ -15,6 +15,9 @@ { oid => '2', oid_symbol => 'HEAP_TABLE_AM_OID', descr => 'heap table access method', amname => 'heap', amhandler => 'heap_tableam_handler', amtype => 't' }, +{ oid => '8051', oid_symbol => 'LOCAL_SEQUENCE_AM_OID', + descr => 'local sequence access method', + amname => 'seqlocal', amhandler => 'seq_local_sequenceam_handler', amtype => 's' }, { oid => '403', oid_symbol => 'BTREE_AM_OID', descr => 'b-tree index access method', amname => 'btree', amhandler => 'bthandler', amtype => 'i' }, diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h index 6e98a0930c27..080bea5031a9 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 */ #endif /* EXPOSE_TO_CLIENT_CODE */ diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index f0d612ca4877..f217c8d8fb07 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) == RELKIND_TOASTVALUE || \ (relkind) == RELKIND_MATVIEW) +/* + * Relation kinds with a sequence access method (rd_sequenceam). + */ +#define RELKIND_HAS_SEQUENCE_AM(relkind) \ + ((relkind) == RELKIND_SEQUENCE) + extern int errdetail_relkind_not_supported(char relkind); #endif /* EXPOSE_TO_CLIENT_CODE */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 9e803d610d7b..71ba82be775a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -913,6 +913,12 @@ prorettype => 'table_am_handler', proargtypes => 'internal', prosrc => 'heap_tableam_handler' }, +# Sequence access method handlers +{ oid => '8209', descr => 'local sequence access method handler', + proname => 'seq_local_sequenceam_handler', provolatile => 'v', + prorettype => 'sequence_am_handler', proargtypes => 'internal', + prosrc => 'seq_local_sequenceam_handler' }, + # Index access method handlers { oid => '330', descr => 'btree index access method handler', proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler', @@ -7803,6 +7809,13 @@ { oid => '327', descr => 'I/O', proname => 'index_am_handler_out', prorettype => 'cstring', proargtypes => 'index_am_handler', prosrc => 'index_am_handler_out' }, +{ oid => '8207', descr => 'I/O', + proname => 'sequence_am_handler_in', proisstrict => 'f', + prorettype => 'sequence_am_handler', proargtypes => 'cstring', + prosrc => 'sequence_am_handler_in' }, +{ oid => '8208', descr => 'I/O', + proname => 'sequence_am_handler_out', prorettype => 'cstring', + proargtypes => 'sequence_am_handler', prosrc => 'sequence_am_handler_out' }, { oid => '3311', descr => 'I/O', proname => 'tsm_handler_in', proisstrict => 'f', prorettype => 'tsm_handler', proargtypes => 'cstring', prosrc => 'tsm_handler_in' }, diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat index 6dca77e0a22f..c42531b25533 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -627,6 +627,12 @@ typcategory => 'P', typinput => 'index_am_handler_in', typoutput => 'index_am_handler_out', typreceive => '-', typsend => '-', typalign => 'i' }, +{ oid => '8210', + descr => 'pseudo-type for the result of a sequence AM handler function', + typname => 'sequence_am_handler', typlen => '4', typbyval => 't', + typtype => 'p', typcategory => 'P', typinput => 'sequence_am_handler_in', + typoutput => 'sequence_am_handler_out', typreceive => '-', typsend => '-', + typalign => 'i' }, { oid => '3310', descr => 'pseudo-type for the result of a tablesample method function', typname => 'tsm_handler', typlen => '4', typbyval => 't', typtype => 'p', diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 6d9348bac804..eb2e344a184f 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); diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index 9ac0b67683d3..7693e9941fc9 100644 --- a/src/include/commands/sequence.h +++ b/src/include/commands/sequence.h @@ -22,35 +22,6 @@ #include "storage/relfilelocator.h" -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); -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 d1ca24dd32f0..b1c4155c9a91 100644 --- a/src/include/nodes/meson.build +++ b/src/include/nodes/meson.build @@ -10,6 +10,7 @@ node_support_input_i = [ 'access/amapi.h', 'access/cmptype.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 30cd7188c5c1..95e67fd1235e 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3196,6 +3196,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; diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 87999218d687..7464471f15d1 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, 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 **extra, + 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 db3e504c3d2d..f2155c1195d8 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; + /* + * 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/sequence/Makefile index a15ceec1c0a0..62006165a15f 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = seqlocalam.o seqlocalxlog.o sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o sequenceamapi.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index 8bc0e95e68c0..d82af34d538c 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -4,4 +4,5 @@ backend_sources += files( 'seqlocalam.c', 'seqlocalxlog.c', 'sequence.c', + 'sequenceamapi.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c index bc735ebb0838..946c7f87a686 100644 --- a/src/backend/access/sequence/seqlocalam.c +++ b/src/backend/access/sequence/seqlocalam.c @@ -17,6 +17,7 @@ #include "access/multixact.h" #include "access/seqlocalam.h" +#include "access/sequenceam.h" #include "access/xact.h" #include "access/xloginsert.h" #include "access/xlogutils.h" @@ -24,6 +25,7 @@ #include "commands/tablecmds.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "utils/builtins.h" /* @@ -230,10 +232,10 @@ fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) * Allocate a new value for a local sequence, based on the sequence * configuration. */ -int64 +static int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last) + int64 minv, int64 cache, bool cycle, + int64 *last) { int64 result; int64 fetch; @@ -417,7 +419,7 @@ seq_local_nextval(Relation rel, int64 incby, int64 maxv, * * Return the table access method used by this sequence. */ -const char * +static const char * seq_local_get_table_am(void) { return "heap"; @@ -432,7 +434,7 @@ seq_local_get_table_am(void) * inserted after the relation has been created, filling in its heap * table. */ -void +static void seq_local_init(Relation rel, int64 last_value, bool is_called) { Datum value[SEQ_LOCAL_COL_LASTCOL]; @@ -499,7 +501,7 @@ seq_local_init(Relation rel, int64 last_value, bool is_called) * * Callback for setval(). */ -void +static void seq_local_setval(Relation rel, int64 next, bool iscalled) { Buffer buf; @@ -547,7 +549,7 @@ seq_local_setval(Relation rel, int64 next, bool iscalled) * Perform a hard reset on the local sequence, rewriting its heap data * entirely. */ -void +static void seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) { Form_pg_seq_local_data seq; @@ -600,7 +602,7 @@ seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) * * Retrieve the state of a local sequence. */ -void +static void seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) { Buffer buf; @@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) * * Persistence change for the local sequence Relation. */ -void +static void seq_local_change_persistence(Relation rel, char newrelpersistence) { Buffer buf; @@ -632,3 +634,24 @@ seq_local_change_persistence(Relation rel, char newrelpersistence) fill_seq_with_data(rel, &seqdatatuple); UnlockReleaseBuffer(buf); } + +/* ------------------------------------------------------------------------ + * Definition of the local sequence access method. + * ------------------------------------------------------------------------ + */ +static const SequenceAmRoutine seq_local_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = seq_local_get_table_am, + .init = seq_local_init, + .nextval = seq_local_nextval, + .setval = seq_local_setval, + .reset = seq_local_reset, + .get_state = seq_local_get_state, + .change_persistence = seq_local_change_persistence +}; + +Datum +seq_local_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&seq_local_methods); +} diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/sequence.c index 8b5303553702..f30317d1feae 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 sequences - * (in contrast to other relation types like indexes). + * (in contrast to other relation types like indexes) that are independent + * of individual sequence access methods. * *------------------------------------------------------------------------- */ diff --git a/src/backend/access/sequence/sequenceamapi.c b/src/backend/access/sequence/sequenceamapi.c new file mode 100644 index 000000000000..dd1a60d827a4 --- /dev/null +++ b/src/backend/access/sequence/sequenceamapi.c @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------- + * + * sequenceamapi.c + * general sequence access method routines + * + * Portions Copyright (c) 1996-2024, 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 = 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 = OidFunctionCall0(amhandler); + routine = (SequenceAmRoutine *) DatumGetPointer(datum); + + if (routine == NULL || !IsA(routine, SequenceAmRoutine)) + elog(ERROR, "sequence access method handler %u did not return a SequenceAmRoutine 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 != NULL); + Assert(routine->init != NULL); + Assert(routine->nextval != NULL); + Assert(routine->setval != NULL); + Assert(routine->reset != NULL); + Assert(routine->get_state != NULL); + Assert(routine->change_persistence != 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 = SearchSysCache1(AMOID, + ObjectIdGetDatum(amoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", amoid); + aform = (Form_pg_am) GETSTRUCT(tuple); + Assert(aform->amtype == AMTYPE_SEQUENCE); + amhandleroid = 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 == '\0') + { + GUC_check_errdetail("%s cannot be empty.", + "default_sequence_access_method"); + return false; + } + + if (strlen(*newval) >= 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 != InvalidOid) + { + if (!OidIsValid(get_sequence_am_oid(*newval, true))) + { + /* + * When source == 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 == 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 956f196fc95c..4d06010b9a88 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -1477,9 +1477,13 @@ 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. Partitioned tables may not have an * access method set. + * + * Sequences and tables are created with their access method ID + * given by the caller of this function. */ if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) || - (relkind == RELKIND_PARTITIONED_TABLE && OidIsValid(accessmtd))) + (relkind == RELKIND_PARTITIONED_TABLE && OidIsValid(accessmtd)) || + 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 58ed9d216cc0..e44633d13b60 100644 --- a/src/backend/commands/amcmds.c +++ b/src/backend/commands/amcmds.c @@ -15,6 +15,7 @@ #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); } +/* + * 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 = INDEX_AM_HANDLEROID; break; + case AMTYPE_SEQUENCE: + expectedType = SEQUENCE_AM_HANDLEROID; + break; case AMTYPE_TABLE: expectedType = TABLE_AM_HANDLEROID; break; diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index b7ed47d92801..e88ace9b4a4d 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,10 +16,10 @@ #include "access/bufmask.h" #include "access/htup_details.h" -#include "access/seqlocalam.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 = NIL; stmt->constraints = NIL; stmt->options = NIL; + stmt->accessMethod = seq->accessMethod ? pstrdup(seq->accessMethod) : NULL; stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; @@ -173,7 +174,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) rel = sequence_open(seqoid, AccessExclusiveLock); /* now initialize the sequence table structure and its data */ - seq_local_init(rel, last_value, is_called); + sequence_init(rel, last_value, is_called); /* process OWNED BY if given */ if (owned_by) @@ -241,7 +242,7 @@ ResetSequence(Oid seq_relid) ReleaseSysCache(pgstuple); /* Sequence state is forcibly reset here. */ - seq_local_reset(seq_rel, startv, false, true); + sequence_reset(seq_rel, startv, false, true); /* 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 = (Form_pg_sequence) GETSTRUCT(seqtuple); /* Read sequence data */ - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); /* 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(); - seq_local_reset(seqrel, last_value, is_called, reset_state); + sequence_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -353,7 +354,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - seq_local_change_persistence(seqrel, newrelpersistence); + sequence_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -470,8 +471,8 @@ nextval_internal(Oid relid, bool check_permissions) ReleaseSysCache(pgstuple); /* retrieve next value from the access method */ - result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, - &last); + result = sequence_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -625,7 +626,7 @@ do_setval(Oid relid, int64 next, bool iscalled) GetTopTransactionId(); /* Call the access method callback */ - seq_local_setval(seqrel, next, iscalled); + sequence_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS) bool is_called; int64 last_value; - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); values[0] = Int64GetDatum(last_value); values[1] = BoolGetDatum(is_called); @@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - seq_local_get_state(seqrel, &result, &is_called); + sequence_get_state(seqrel, &result, &is_called); } sequence_close(seqrel, NoLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index e466508c1d3b..6102966d8d6a 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" @@ -998,14 +999,18 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, } /* - * 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 != NULL) { - Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE); - accessMethodId = get_table_am_oid(stmt->accessMethod, false); + Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE || + RELKIND_HAS_SEQUENCE_AM(relkind)); + if (RELKIND_HAS_SEQUENCE_AM(relkind)) + accessMethodId = get_sequence_am_oid(stmt->accessMethod, false); + else + accessMethodId = get_table_am_oid(stmt->accessMethod, false); } else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE) { @@ -1018,6 +1023,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId)) accessMethodId = get_table_am_oid(default_table_access_method, false); } + else if (RELKIND_HAS_SEQUENCE_AM(relkind)) + { + accessMethodId = get_sequence_am_oid(default_sequence_access_method, false); + } /* * Create the relation. Inherited defaults and CHECK constraints are diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile index 77ddb9ca53f1..64d4dccc936f 100644 --- a/src/backend/nodes/Makefile +++ b/src/backend/nodes/Makefile @@ -48,6 +48,7 @@ node_headers = \ access/amapi.h \ access/cmptype.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 1a657f7e0aea..162ee16dbf6d 100644 --- a/src/backend/nodes/gen_node_support.pl +++ b/src/backend/nodes/gen_node_support.pl @@ -60,6 +60,7 @@ my @all_input_files = qw( access/amapi.h access/cmptype.h access/sdir.h + access/sequenceam.h access/tableam.h access/tsmapi.h commands/event_trigger.h @@ -84,6 +85,7 @@ my @nodetag_only_files = 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 d3887628d469..b5e72aeec8fb 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -381,6 +381,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <str> copy_file_name access_method_clause attr_name table_access_method_clause name cursor_name file_name + sequence_access_method_clause cluster_index_specification %type <list> func_name handler_name qual_Op qual_all_Op subquery_Op @@ -4919,23 +4920,26 @@ RefreshMatViewStmt: CreateSeqStmt: CREATE OptTemp SEQUENCE qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); - $4->relpersistence = $2; n->sequence = $4; n->options = $5; + n->accessMethod = $6; n->ownerId = InvalidOid; n->if_not_exists = false; $$ = (Node *) n; } | CREATE OptTemp SEQUENCE IF_P NOT EXISTS qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); $7->relpersistence = $2; n->sequence = $7; n->options = $8; + n->accessMethod = $9; n->ownerId = InvalidOid; n->if_not_exists = true; $$ = (Node *) n; @@ -4972,6 +4976,11 @@ OptParenthesizedSeqOptList: '(' SeqOptList ')' { $$ = $2; } | /*EMPTY*/ { $$ = NIL; } ; +sequence_access_method_clause: + USING name { $$ = $2; } + | /*EMPTY*/ { $$ = NULL; } + ; + SeqOptList: SeqOptElem { $$ = list_make1($1); } | SeqOptList SeqOptElem { $$ = lappend($1, $2); } ; @@ -5975,6 +5984,7 @@ CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name am_type: INDEX { $$ = AMTYPE_INDEX; } + | SEQUENCE { $$ = AMTYPE_SEQUENCE; } | TABLE { $$ = AMTYPE_TABLE; } ; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index eb7716cd84c9..8fcad04428e5 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" @@ -518,6 +519,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, seqstmt->sequence = makeRangeVar(snamespace, sname, -1); seqstmt->sequence->relpersistence = seqpersistence; seqstmt->options = seqoptions; + seqstmt->accessMethod = NULL; /* * 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/pseudotypes.c index 317a1f2b282f..68f160dda7d3 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/relcache.c index 398114373e9f..c10e77372071 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" @@ -302,6 +304,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); @@ -1196,8 +1199,7 @@ retry: if (relation->rd_rel->relkind == RELKIND_INDEX || relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) RelationInitIndexAccessInfo(relation); - else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || - relation->rd_rel->relkind == RELKIND_SEQUENCE) + else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)) RelationInitTableAccessMethod(relation); else if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { @@ -1206,6 +1208,8 @@ retry: * inherit. */ } + else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind)) + RelationInitSequenceAccessMethod(relation); else Assert(relation->rd_rel->relam == InvalidOid); @@ -1802,17 +1806,9 @@ RelationInitTableAccessMethod(Relation relation) HeapTuple tuple; Form_pg_am aform; - if (relation->rd_rel->relkind == 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 == InvalidOid); - relation->rd_amhandler = 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. @@ -1843,6 +1839,49 @@ RelationInitTableAccessMethod(Relation relation) InitTableAmRoutine(relation); } +/* + * 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 != InvalidOid); + relation->rd_amhandler = GetSequenceAmRoutineId(relation->rd_rel->relam); + + /* + * Now we can fetch the sequence AM's API struct. + */ + relation->rd_sequenceam = GetSequenceAmRoutine(relation->rd_amhandler); + + /* + * From the sequence AM, set its expected table access method. + */ + tableam_name = sequence_get_table_am(relation); + tableam_oid = get_table_am_oid(tableam_name, false); + + tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(tableam_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", + tableam_oid); + aform = (Form_pg_am) GETSTRUCT(tuple); + tableam_handler = aform->amhandler; + ReleaseSysCache(tuple); + + relation->rd_tableam = GetTableAmRoutine(tableam_handler); +} + /* * formrdesc * @@ -3659,14 +3698,17 @@ RelationBuildLocalRelation(const char *relname, rel->rd_rel->relam = accessmtd; /* - * 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); - if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(relkind)) RelationInitTableAccessMethod(rel); + else if (relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); /* * Leave index access method uninitialized, because the pg_index row has @@ -4290,13 +4332,21 @@ RelationCacheInitializePhase3(void) /* Reload tableam data if needed */ if (relation->rd_tableam == NULL && - (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE)) + (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))) { RelationInitTableAccessMethod(relation); Assert(relation->rd_tableam != NULL); restart = true; } + else if (relation->rd_sequenceam == NULL && + relation->rd_rel->relkind == RELKIND_SEQUENCE) + { + RelationInitSequenceAccessMethod(relation); + Assert(relation->rd_sequenceam != NULL); + + restart = true; + } /* Release hold on the relation */ RelationDecrementReferenceCount(relation); @@ -6348,8 +6398,10 @@ load_relcache_init_file(bool shared) nailed_rels++; /* Load table AM data */ - if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || rel->rd_rel->relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind)) RelationInitTableAccessMethod(rel); + else if (rel->rd_rel->relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); Assert(rel->rd_index == NULL); Assert(rel->rd_indextuple == NULL); @@ -6361,6 +6413,7 @@ load_relcache_init_file(bool shared) Assert(rel->rd_supportinfo == NULL); Assert(rel->rd_indoption == NULL); Assert(rel->rd_indcollation == NULL); + Assert(rel->rd_sequenceam == NULL); Assert(rel->rd_opcoptions == NULL); } diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index cce733146090..6a9f880da169 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" @@ -4252,6 +4253,17 @@ struct config_string ConfigureNamesString[] = check_default_table_access_method, NULL, NULL }, + { + {"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default sequence access method for new sequences."), + 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/utils/misc/postgresql.conf.sample index d472987ed46a..a6426a7a2fb3 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -725,6 +725,7 @@ autovacuum_worker_slots = 16 # autovacuum worker slots to allocate # error #search_path = '"$user", public' # schema names #row_security = on +#default_sequence_access_method = 'seqlocal' #default_table_access_method = 'heap' #default_tablespace = '' # a tablespace name, '' uses the default #default_toast_compression = 'pglz' # 'pglz' or 'lz4' diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 3b7ba66fad05..6cdb1b075625 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -168,10 +168,12 @@ describeAccessMethods(const char *pattern, bool verbose) "SELECT amname AS \"%s\",\n" " CASE amtype" " WHEN " CppAsString2(AMTYPE_INDEX) " THEN '%s'" + " WHEN " CppAsString2(AMTYPE_SEQUENCE) " THEN '%s'" " WHEN " CppAsString2(AMTYPE_TABLE) " THEN '%s'" " END AS \"%s\"", gettext_noop("Name"), gettext_noop("Index"), + gettext_noop("Sequence"), gettext_noop("Table"), gettext_noop("Type")); diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index eb8bc1287202..f4b61948f8e1 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -2561,7 +2561,7 @@ match_previous_words(int pattern_id, 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 <name> AS */ else if (TailMatches("ALTER", "SEQUENCE", MatchAny, "AS")) COMPLETE_WITH_CS("smallint", "integer", "bigint"); @@ -3601,7 +3601,7 @@ match_previous_words(int pattern_id, 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/expected/create_am.out index c1a951572512..784870e603d1 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; @@ -514,9 +509,12 @@ CREATE TABLE tableam_parted_heapx (a text, b int) PARTITION 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 = 'seqlocal'; 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; @@ -533,18 +531,18 @@ FROM pg_class AS pc LEFT JOIN pg_am AS pa ON (pa.oid = pc.relam) WHERE pc.relname LIKE 'tableam_%_heapx' ORDER BY 3, 1, 2; - relkind | amname | relname ----------+--------+----------------------------- - f | | tableam_fdw_heapx - r | heap2 | tableam_parted_1_heapx - r | heap | tableam_parted_2_heapx - p | | tableam_parted_heapx - S | | tableam_seq_heapx - r | heap2 | tableam_tbl_heapx - r | heap2 | tableam_tblas_heapx - m | heap2 | tableam_tblmv_heapx - r | heap2 | tableam_tblselectinto_heapx - v | | tableam_view_heapx + relkind | amname | relname +---------+----------+----------------------------- + f | | tableam_fdw_heapx + r | heap2 | tableam_parted_1_heapx + r | heap | tableam_parted_2_heapx + p | | tableam_parted_heapx + S | seqlocal | tableam_seq_heapx + r | heap2 | tableam_tbl_heapx + r | heap2 | tableam_tblas_heapx + m | heap2 | tableam_tblmv_heapx + r | heap2 | tableam_tblselectinto_heapx + v | | tableam_view_heapx (10 rows) -- don't want to keep those tables, nor the default @@ -574,3 +572,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_dump support +-- Checks for sequence access methods +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_local_sequenceam_handler; +-- Create and use sequence +CREATE SEQUENCE test_seqam USING local2; +SELECT nextval('test_seqam'::regclass); + nextval +--------- + 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/expected/opr_sanity.out index b673642ad1d7..3dedeff17201 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1940,6 +1940,18 @@ WHERE p1.oid = a1.amhandler AND a1.amtype = 't' AND -----+--------+-----+--------- (0 rows) +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + oid | amname | oid | proname +-----+--------+-----+--------- +(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 f9db4032e1f1..2cb070b12366 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -5120,31 +5120,33 @@ Indexes: -- check printing info about access methods \dA List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA * List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA h* List of access methods @@ -5169,32 +5171,34 @@ List of access methods \dA: extra argument "bar" ignored \dA+ - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ * - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ h* List of access methods diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out index 8eff3d10d279..622f360d5b3c 100644 --- a/src/test/regress/expected/type_sanity.out +++ b/src/test/regress/expected/type_sanity.out @@ -507,21 +507,21 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p', 'I') OR -----+--------- (0 rows) --- All tables, indexes, partitioned indexes and matviews should have an --- access method. +-- All tables, indexes, partitioned indexes, matviews and sequences should have +-- an access method. SELECT c1.oid, c1.relname FROM pg_class as c1 -WHERE c1.relkind NOT IN ('S', 'v', 'f', 'c', 'p') and +WHERE c1.relkind NOT IN ('v', 'f', 'c', 'p') and c1.relam = 0; oid | relname -----+--------- (0 rows) --- Conversely, sequences, views, foreign tables, types and partitioned --- tables shouldn't have them. +-- Conversely, views, foreign tables, types and partitioned tables +-- shouldn't have them. SELECT c1.oid, c1.relname FROM pg_class as c1 -WHERE c1.relkind IN ('S', 'v', 'f', 'c', 'p') and +WHERE c1.relkind IN ('v', 'f', 'c', 'p') and c1.relam != 0; oid | relname -----+--------- diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql index 754fe0c694bc..76a91cf8dd68 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 FROM tableam_tbl_heap2; -- CREATE VIEW doesn't support USING CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2; --- 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; @@ -327,9 +324,13 @@ CREATE TABLE tableam_parted_1_heapx PARTITION OF tableam_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; --- 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 = 'seqlocal'; 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; @@ -365,3 +366,16 @@ CREATE FOREIGN TABLE fp PARTITION OF tableam_parted_a_heap2 DEFAULT SERVER x; DROP ACCESS METHOD heap2; -- we intentionally leave the objects created above alive, to verify pg_dump support + +-- Checks for sequence access methods + +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_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 2fb3a8528781..e60eeab33c02 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -1236,6 +1236,16 @@ WHERE p1.oid = a1.amhandler AND a1.amtype = 't' AND OR p1.pronargs != 1 OR p1.proargtypes[0] != 'internal'::regtype); +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + -- **************** pg_amop **************** -- Look for illegal values in pg_amop fields diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql index 303f90955d15..aade05c0e172 100644 --- a/src/test/regress/sql/type_sanity.sql +++ b/src/test/regress/sql/type_sanity.sql @@ -364,18 +364,18 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p', 'I') OR relpersistence NOT IN ('p', 'u', 't') OR relreplident NOT IN ('d', 'n', 'f', 'i'); --- All tables, indexes, partitioned indexes and matviews should have an --- access method. +-- All tables, indexes, partitioned indexes, matviews and sequences should have +-- an access method. SELECT c1.oid, c1.relname FROM pg_class as c1 -WHERE c1.relkind NOT IN ('S', 'v', 'f', 'c', 'p') and +WHERE c1.relkind NOT IN ('v', 'f', 'c', 'p') and c1.relam = 0; --- Conversely, sequences, views, foreign tables, types and partitioned --- tables shouldn't have them. +-- Conversely, views, foreign tables, types and partitioned tables +-- shouldn't have them. SELECT c1.oid, c1.relname FROM pg_class as c1 -WHERE c1.relkind IN ('S', 'v', 'f', 'c', 'p') and +WHERE c1.relkind IN ('v', 'f', 'c', 'p') and c1.relam != 0; -- Indexes and partitioned indexes should have AMs of type 'i'. diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index bce4214503d6..ed72179081e9 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2601,6 +2601,7 @@ SeqScanState SeqTable SeqTableData SeqType +SequenceAmRoutine SequenceItem SerCommitSeqNo SerialControl @@ -3638,6 +3639,7 @@ list_sort_comparator loc_chunk local_relopt local_relopts +local_sequence_magic local_source local_ts_iter local_ts_radix_tree @@ -3926,7 +3928,6 @@ scram_state_enum script_error_callback_arg security_class_t sem_t -sepgsql_context_info_t sequence_magic set_join_pathlist_hook_type set_rel_pathlist_hook_type @@ -4151,6 +4152,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 @@ -4162,7 +4164,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 -- 2.47.2 [text/x-diff] v11-0005-Sequence-access-methods-dump-restore-support.patch (21.8K, ../../[email protected]/6-v11-0005-Sequence-access-methods-dump-restore-support.patch) download | inline diff: From 737b215d4aa9b364c211c9023b89dfa082f253d7 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Mon, 26 Aug 2024 13:25:19 +0900 Subject: [PATCH v11 5/7] 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 | 47 +++++++++++++++----- 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 | 49 +++++++++++++++++++++ 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, 207 insertions(+), 11 deletions(-) diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index f0f19bb0b291..2b8298f19414 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -96,6 +96,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 @@ -186,6 +187,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_backup_archiver.c index b9d7ab98c3e6..46d90892b103 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -176,6 +176,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) dopt->outputSuperuser = ropt->superuser; dopt->outputCreateDB = ropt->createDB; dopt->outputNoOwner = ropt->noOwner; + dopt->outputNoSequenceAm = ropt->noSequenceAm; dopt->outputNoTableAm = ropt->noTableAm; dopt->outputNoTablespaces = ropt->noTablespace; dopt->disable_triggers = ropt->disable_triggers; @@ -1228,6 +1229,7 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId, newToc->tag = pg_strdup(opts->tag); newToc->namespace = opts->namespace ? pg_strdup(opts->namespace) : NULL; newToc->tablespace = opts->tablespace ? pg_strdup(opts->tablespace) : NULL; + newToc->sequenceam = opts->sequenceam ? pg_strdup(opts->sequenceam) : NULL; newToc->tableam = opts->tableam ? pg_strdup(opts->tableam) : NULL; newToc->relkind = opts->relkind; newToc->owner = opts->owner ? pg_strdup(opts->owner) : NULL; @@ -2383,6 +2385,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, AH->currUser = NULL; /* unknown */ AH->currSchema = NULL; /* ditto */ + AH->currSequenceAm = NULL; /* ditto */ AH->currTablespace = NULL; /* ditto */ AH->currTableAm = NULL; /* ditto */ @@ -2612,6 +2615,7 @@ WriteToc(ArchiveHandle *AH) WriteStr(AH, te->copyStmt); WriteStr(AH, te->namespace); WriteStr(AH, te->tablespace); + WriteStr(AH, te->sequenceam); WriteStr(AH, te->tableam); WriteInt(AH, te->relkind); WriteStr(AH, te->owner); @@ -2716,6 +2720,9 @@ ReadToc(ArchiveHandle *AH) if (AH->version >= K_VERS_1_10) te->tablespace = ReadStr(AH); + if (AH->version >= K_VERS_1_17) + te->sequenceam = ReadStr(AH); + if (AH->version >= K_VERS_1_14) te->tableam = ReadStr(AH); @@ -3372,6 +3379,9 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname) free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; + free(AH->currTableAm); AH->currTableAm = NULL; @@ -3534,6 +3544,57 @@ _selectTablespace(ArchiveHandle *AH, const char *tablespace) destroyPQExpBuffer(qry); } +/* + * Set the proper default_sequence_access_method value for the sequence. + */ +static void +_selectSequenceAccessMethod(ArchiveHandle *AH, const char *sequenceam) +{ + RestoreOptions *ropt = AH->public.ropt; + PQExpBuffer cmd; + const char *want, + *have; + + /* do nothing in --no-sequence-access-method mode */ + if (ropt->noSequenceAm) + return; + + have = AH->currSequenceAm; + want = sequenceam; + + if (!want) + return; + + if (have && strcmp(want, have) == 0) + return; + + cmd = createPQExpBuffer(); + appendPQExpBuffer(cmd, + "SET default_sequence_access_method = %s;", + fmtId(want)); + + if (RestoringToDB(AH)) + { + PGresult *res; + + res = PQexec(AH->connection, cmd->data); + + if (!res || PQresultStatus(res) != 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 = pg_strdup(want); +} + /* * Set the proper default_table_access_method value for the table. */ @@ -3743,6 +3804,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData) _becomeOwner(AH, te); _selectOutputSchema(AH, te->namespace); _selectTablespace(AH, te->tablespace); + _selectSequenceAccessMethod(AH, te->sequenceam); if (te->relkind != RELKIND_PARTITIONED_TABLE) _selectTableAccessMethod(AH, te->tableam); @@ -4267,6 +4329,8 @@ restore_toc_entries_prefork(ArchiveHandle *AH, TocEntry *pending_list) AH->currUser = NULL; free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; free(AH->currTablespace); AH->currTablespace = NULL; free(AH->currTableAm); @@ -5006,6 +5070,7 @@ CloneArchive(ArchiveHandle *AH) clone->connCancel = NULL; clone->currUser = NULL; clone->currSchema = NULL; + clone->currSequenceAm = NULL; clone->currTableAm = NULL; clone->currTablespace = NULL; @@ -5065,6 +5130,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_backup_archiver.h index ce5ed1dd395d..cdfebcd34047 100644 --- a/src/bin/pg_dump/pg_backup_archiver.h +++ b/src/bin/pg_dump/pg_backup_archiver.h @@ -71,10 +71,11 @@ #define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0) /* BLOB METADATA entries * and multiple BLOBS, * relkind */ +#define K_VERS_1_17 MAKE_ARCHIVE_VERSION(1, 17, 0) /* add sequenceam */ /* 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_VERS_REV) @@ -322,6 +323,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 */ @@ -353,6 +355,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 relkind; /* relation kind, only for TABLE tags */ char *owner; @@ -394,6 +397,7 @@ typedef struct _archiveOpts const char *tag; const char *namespace; const char *tablespace; + const char *sequenceam; const char *tableam; char relkind; const char *owner; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 30dfda8c3ffc..e0e8430515e0 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -133,6 +133,7 @@ typedef struct int64 cache; /* cache size */ int64 last_value; /* last value of sequence */ bool is_called; /* whether nextval advances before returning */ + char *seqam; /* access method of sequence */ } SequenceItem; typedef enum OidOptions @@ -482,6 +483,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}, @@ -1107,6 +1109,7 @@ main(int argc, char **argv) ropt->superuser = dopt.outputSuperuser; ropt->createDB = dopt.outputCreateDB; ropt->noOwner = dopt.outputNoOwner; + ropt->noSequenceAm = dopt.outputNoSequenceAm; ropt->noTableAm = dopt.outputNoTableAm; ropt->noTablespace = dopt.outputNoTablespaces; ropt->disable_triggers = dopt.disable_triggers; @@ -1223,6 +1226,7 @@ help(const char *progname) printf(_(" --no-publications do not dump publications\n")); printf(_(" --no-security-labels do not dump security label assignments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); + printf(_(" --no-sequence-access-method do not sequence table access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); @@ -13293,6 +13297,9 @@ dumpAccessMethod(Archive *fout, const AccessMethodInfo *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; @@ -17682,26 +17689,40 @@ collectSequences(Archive *fout) * * Since version 18, we can gather the sequence data in this query with * pg_get_sequence_data(), but we only do so for non-schema-only dumps. + * + * Access methods for sequences are supported since version 18. */ if (fout->remoteVersion < 100000) return; - else if (fout->remoteVersion < 180000 || - (!fout->dopt->dumpData && !fout->dopt->sequence_data)) + else if (fout->remoteVersion < 180000) query = "SELECT seqrelid, format_type(seqtypid, NULL), " "seqstart, seqincrement, " "seqmax, seqmin, " "seqcache, seqcycle, " - "NULL, 'f' " + "NULL, 'f', NULL " "FROM pg_catalog.pg_sequence " "ORDER BY seqrelid"; + else if (!fout->dopt->dumpData && !fout->dopt->sequence_data) + query = "SELECT s.seqrelid, format_type(s.seqtypid, NULL), " + "s.seqstart, s.seqincrement, " + "s.seqmax, s.seqmin, " + "s.seqcache, s.seqcycle, " + "NULL, 'f', a.amname AS seqam " + "FROM pg_catalog.pg_sequence s " + "JOIN pg_class c ON (c.oid = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam) " + "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_get_sequence_data(seqrelid) " + query = "SELECT s.seqrelid, format_type(s.seqtypid, NULL), " + "s.seqstart, s.seqincrement, " + "s.seqmax, s.seqmin, " + "s.seqcache, s.seqcycle, " + "r.last_value, r.is_called, " + "a.amname AS seqam " + "FROM pg_catalog.pg_sequence s " + "JOIN pg_class c ON (c.oid = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam), " + "pg_get_sequence_data(s.seqrelid) r " "ORDER BY seqrelid;"; res = ExecuteSqlQuery(fout, query, PGRES_TUPLES_OK); @@ -17721,6 +17742,10 @@ collectSequences(Archive *fout) 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); + if (!PQgetisnull(res, i, 10)) + sequences[i].seqam = pg_strdup(PQgetvalue(res, i, 10)); + else + sequences[i].seqam = NULL; } PQclear(res); @@ -17792,6 +17817,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) 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); + seq->seqam = NULL; PQclear(res); } @@ -17914,6 +17940,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) ARCHIVE_OPTS(.tag = tbinfo->dobj.name, .namespace = tbinfo->dobj.namespace->dobj.name, .owner = tbinfo->rolname, + .sequenceam = seq->seqam, .description = "SEQUENCE", .section = SECTION_PRE_DATA, .createStmt = query->data, diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index 64a60a260928..f834cff3baf7 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 = 0; static int disable_triggers = 0; static int if_exists = 0; static int inserts = 0; +static int no_sequence_access_method = 0; static int no_table_access_method = 0; static int no_tablespaces = 0; static int use_setsessauth = 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) @@ -669,6 +673,7 @@ help(void) printf(_(" --no-security-labels do not dump security label assignments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); printf(_(" --no-sync do not wait for changes to be written safely to disk\n")); + printf(_(" --no-sequence-access-method do not dump sequence access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c index c602272d7dbb..4907c494bcdf 100644 --- a/src/bin/pg_dump/pg_restore.c +++ b/src/bin/pg_dump/pg_restore.c @@ -67,6 +67,7 @@ main(int argc, char **argv) static int enable_row_security = 0; static int if_exists = 0; static int no_data_for_failed_tables = 0; + static int outputNoSequenceAm = 0; static int outputNoTableAm = 0; static int outputNoTablespaces = 0; static int use_setsessauth = 0; @@ -116,6 +117,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}, @@ -368,6 +370,7 @@ main(int argc, char **argv) opts->disable_triggers = disable_triggers; opts->enable_row_security = enable_row_security; opts->noDataForFailedTables = no_data_for_failed_tables; + opts->noSequenceAm = outputNoSequenceAm; opts->noTableAm = outputNoTableAm; opts->noTablespace = outputNoTablespaces; opts->use_setsessauth = use_setsessauth; @@ -494,6 +497,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 access methods\n")); printf(_(" --no-table-access-method do not restore table access methods\n")); printf(_(" --no-tablespaces do not restore tablespace assignments\n")); printf(_(" --section=SECTION restore named section (pre-data, 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_dump.pl index bc5d9222a208..888ac6d0f191 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -595,6 +595,13 @@ my %pgdump_runs = ( 'postgres', ], }, + no_sequence_access_method => { + dump_cmd => [ + 'pg_dump', '--no-sync', + "--file=$tempdir/no_sequence_access_method.sql", + '--no-sequence-access-method', 'postgres', + ], + }, no_table_access_method => { dump_cmd => [ 'pg_dump', '--no-sync', @@ -776,6 +783,7 @@ my %full_runs = ( no_large_objects => 1, no_owner => 1, no_privs => 1, + no_sequence_access_method => 1, no_table_access_method => 1, pg_dumpall_dbprivs => 1, pg_dumpall_exclude => 1, @@ -4601,6 +4609,18 @@ my %tests = ( }, }, + 'CREATE ACCESS METHOD regress_test_sequence_am' => { + create_order => 11, + create_sql => + 'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;', + regexp => qr/^ + \QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;\E + \n/xm, + like => { + %full_runs, section_pre_data => 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 @@ -4629,6 +4649,35 @@ my %tests = ( }, }, + + # 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' => { + create_order => 12, + create_sql => ' + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_0 USING seqlocal; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_1 USING regress_sequence_am; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_2 USING seqlocal;', + regexp => qr/^ + \QSET default_sequence_access_method = 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 => { + %full_runs, %dump_test_schema_runs, section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + no_sequence_access_method => 1, + only_dump_measurement => 1, + }, + }, + 'CREATE MATERIALIZED VIEW regress_pg_dump_matview_am' => { create_order => 13, create_sql => ' diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index 24fcc76d72c2..9f7dd6afe318 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -1121,6 +1121,23 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + + <para> + This option is ignored when emitting an archive (non-text) output + file. For the archive formats, you can specify the option when you + call <command>pg_restore</command>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml index 39d93c2c0e30..af095576f984 100644 --- a/doc/src/sgml/ref/pg_dumpall.sgml +++ b/doc/src/sgml/ref/pg_dumpall.sgml @@ -479,6 +479,17 @@ exclude database <replaceable class="parameter">PATTERN</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml index b8b27e1719ee..922f69eb4abf 100644 --- a/doc/src/sgml/ref/pg_restore.sgml +++ b/doc/src/sgml/ref/pg_restore.sgml @@ -733,6 +733,17 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> -- 2.47.2 [text/x-diff] v11-0006-Sequence-access-methods-core-documentation.patch (9.5K, ../../[email protected]/7-v11-0006-Sequence-access-methods-core-documentation.patch) download | inline diff: From 36b09ab06e77243a0000502ff9b64a6a8326d139 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:55:56 +0900 Subject: [PATCH v11 6/7] 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 336630ce417e..722882327b0f 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -9403,6 +9403,22 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-default-sequence-access-method" xreflabel="default_sequence_access_method"> + <term><varname>default_sequence_access_method</varname> (<type>string</type>) + <indexterm> + <primary><varname>default_sequence_access_method</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + This parameter specifies the default sequence access method to use when + creating sequences if the <command>CREATE SEQUENCE</command> + command does not explicitly specify an access method. The default is + <literal>local</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-default-tablespace" xreflabel="default_tablespace"> <term><varname>default_tablespace</varname> (<type>string</type>) <indexterm> diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 66e6dccd4c9c..4c367bd5fe26 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -95,6 +95,7 @@ <!ENTITY planstats SYSTEM "planstats.sgml"> <!ENTITY tableam SYSTEM "tableam.sgml"> <!ENTITY indexam SYSTEM "indexam.sgml"> +<!ENTITY sequenceam SYSTEM "sequenceam.sgml"> <!ENTITY nls SYSTEM "nls.sgml"> <!ENTITY plhandler SYSTEM "plhandler.sgml"> <!ENTITY fdwhandler SYSTEM "fdwhandler.sgml"> diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml index 7be25c58507f..cc390e613a34 100644 --- a/doc/src/sgml/postgres.sgml +++ b/doc/src/sgml/postgres.sgml @@ -257,6 +257,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 dae43dbaed58..3067dc4d4df0 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 <replaceable class="parameter">name</replaceable> <listitem> <para> This clause specifies the type of access method to define. - Only <literal>TABLE</literal> and <literal>INDEX</literal> - are supported at present. + Only <literal>TABLE</literal>, <literal>SEQUENCE</literal> and + <literal>INDEX</literal> are supported at present. </para> </listitem> </varlistentry> @@ -77,12 +77,15 @@ CREATE ACCESS METHOD <replaceable class="parameter">name</replaceable> declared to take a single argument of type <type>internal</type>, and its return type depends on the type of access method; for <literal>TABLE</literal> access methods, it must - be <type>table_am_handler</type> and for <literal>INDEX</literal> - access methods, it must be <type>index_am_handler</type>. + be <type>table_am_handler</type>; for <literal>INDEX</literal> + access methods, it must be <type>index_am_handler</type>; + for <literal>SEQUENCE</literal>, it must be + <type>sequence_am_handler</type>; 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 <xref linkend="tableam"/> and the index access method - API is described in <xref linkend="indexam"/>. + is described in <xref linkend="tableam"/>, the index access method + API is described in <xref linkend="indexam"/> and the sequence access + method is described in <xref linkend="sequenceam"/>. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/create_sequence.sgml index 1e283f13d15c..52c6096e4ba2 100644 --- a/doc/src/sgml/ref/create_sequence.sgml +++ b/doc/src/sgml/ref/create_sequence.sgml @@ -29,6 +29,7 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceab [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ] + [ USING <replaceable class="parameter">access_method</replaceable> ] </synopsis> </refsynopsisdiv> @@ -263,6 +264,17 @@ SELECT * FROM <replaceable>name</replaceable>; </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>USING</literal> <replaceable class="parameter">access_method</replaceable></term> + <listitem> + <para> + The <literal>USING</literal> option specifies which sequence access + method will be used when generating the sequence numbers. The default + is <literal>local</literal>. + </para> + </listitem> + </varlistentry> </variablelist> </refsect1> diff --git a/doc/src/sgml/sequenceam.sgml b/doc/src/sgml/sequenceam.sgml new file mode 100644 index 000000000000..a96170bfac03 --- /dev/null +++ b/doc/src/sgml/sequenceam.sgml @@ -0,0 +1,80 @@ +<!-- doc/src/sgml/sequenceam.sgml --> + +<chapter id="sequenceam"> + <title>Sequence Access Method Interface Definition</title> + + <indexterm> + <primary>Sequence Access Method</primary> + </indexterm> + <indexterm> + <primary>sequenceam</primary> + <secondary>Sequence Access Method</secondary> + </indexterm> + + <para> + This chapter explains the interface between the core + <productname>PostgreSQL</productname> system and <firstterm>sequence access + methods</firstterm>, 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. + </para> + + <para> + Each sequence access method is described by a row in the + <link linkend="catalog-pg-am"><structname>pg_am</structname></link> system + catalog. The <structname>pg_am</structname> entry specifies a name and a + <firstterm>handler function</firstterm> for the sequence access method. These + entries can be created and deleted using the + <xref linkend="sql-create-access-method"/> and + <xref linkend="sql-drop-access-method"/> SQL commands. + </para> + + <para> + A sequence access method handler function must be declared to accept a single + argument of type <type>internal</type> and to return the pseudo-type + <type>sequence_am_handler</type>. The argument is a dummy value that simply + 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 + <structname>SequenceAmRoutine</structname>, which contains everything that the + core code needs to know to make use of the sequence access method. The return + value needs to be of server lifetime, which is typically achieved by + defining it as a <literal>static const</literal> variable in global + scope. The <structname>SequenceAmRoutine</structname> struct, also called the + access method's <firstterm>API struct</firstterm>, 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 + <structname>SequenceAmRoutine</structname> structure (with comments inside + 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 <ulink url="https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/include/access/sequenceam.h;hb=HEAD"> + <filename>src/include/access/sequenceam.h</filename></ulink> file. + </para> + + <para> + Currently, the way a sequence access method stores data is fairly + unconstrained, and it is possible to use a predefined + <link linkend="tableam">Table Access Method</link> to store sequence + data. + </para> + + <para> + For crash safety, a sequence access method can use + <link linkend="wal"><acronym>WAL</acronym></link>, or a custom + implementation. + If <acronym>WAL</acronym> is chosen, either + <link linkend="generic-wal">Generic WAL Records</link> can be used, or a + <link linkend="custom-rmgr">Custom WAL Resource Manager</link> can be + implemented. + </para> + + <para> + Any developer of a new <literal>sequence access method</literal> can refer to + the existing <literal>local</literal> implementation present in + <filename>src/backend/access/sequence/local.c</filename> for details of + its implementation. + </para> +</chapter> -- 2.47.2 [text/x-diff] v11-0007-snowflake-Add-sequence-AM-based-on-it.patch (28.0K, ../../[email protected]/8-v11-0007-snowflake-Add-sequence-AM-based-on-it.patch) download | inline diff: From 88941a9878ca9cc9e814ded82968ffeb3c48ce25 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 26 Apr 2024 15:09:00 +0900 Subject: [PATCH v11 7/7] snowflake: Add sequence AM based on it This includes documentation and a basic implementation, though this is in a very early stage. --- doc/src/sgml/contrib.sgml | 1 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/snowflake.sgml | 100 ++++ contrib/Makefile | 1 + contrib/meson.build | 1 + contrib/snowflake/.gitignore | 3 + contrib/snowflake/Makefile | 19 + contrib/snowflake/expected/snowflake.out | 73 +++ contrib/snowflake/meson.build | 34 ++ contrib/snowflake/snowflake--1.0.sql | 21 + contrib/snowflake/snowflake.c | 570 +++++++++++++++++++++++ contrib/snowflake/snowflake.control | 5 + contrib/snowflake/sql/snowflake.sql | 29 ++ 13 files changed, 858 insertions(+) create mode 100644 doc/src/sgml/snowflake.sgml create mode 100644 contrib/snowflake/.gitignore create mode 100644 contrib/snowflake/Makefile create mode 100644 contrib/snowflake/expected/snowflake.out create mode 100644 contrib/snowflake/meson.build create mode 100644 contrib/snowflake/snowflake--1.0.sql create mode 100644 contrib/snowflake/snowflake.c create mode 100644 contrib/snowflake/snowflake.control create mode 100644 contrib/snowflake/sql/snowflake.sql diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml index 7c381949a533..7f55133bd2bd 100644 --- a/doc/src/sgml/contrib.sgml +++ b/doc/src/sgml/contrib.sgml @@ -167,6 +167,7 @@ CREATE EXTENSION <replaceable>extension_name</replaceable>; &seg; &sepgsql; &contrib-spi; + &snowflake; &sslinfo; &tablefunc; &tcn; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 4c367bd5fe26..5247bee3224a 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -157,6 +157,7 @@ <!ENTITY seg SYSTEM "seg.sgml"> <!ENTITY contrib-spi SYSTEM "contrib-spi.sgml"> <!ENTITY sepgsql SYSTEM "sepgsql.sgml"> +<!ENTITY snowflake SYSTEM "snowflake.sgml"> <!ENTITY sslinfo SYSTEM "sslinfo.sgml"> <!ENTITY tablefunc SYSTEM "tablefunc.sgml"> <!ENTITY tcn SYSTEM "tcn.sgml"> diff --git a/doc/src/sgml/snowflake.sgml b/doc/src/sgml/snowflake.sgml new file mode 100644 index 000000000000..060699e7ecd1 --- /dev/null +++ b/doc/src/sgml/snowflake.sgml @@ -0,0 +1,100 @@ +<!-- doc/src/sgml/snowflake.sgml --> + +<sect1 id="snowflake" xreflabel="snowflake"> + <title>snowflake — sequence access method</title> + + <indexterm zone="snowflake"> + <primary>snowflake</primary> + </indexterm> + + <para> + <literal>snowflake</literal> provides a sequence access method based on + <ulink url="https://en.wikipedia.org/wiki/Snowflake_ID">Snowflake IDs</ulink>. + </para> + + <para> + A Snowflake ID (or snowflake) is a unique 64-bit identifier made of three + components: + <itemizedlist spacing="compact"> + <listitem><para>41 bits for a timestamp, epoch-adjusted in milli-seconds</para></listitem> + <listitem><para>10 bits for machine ID</para></listitem> + <listitem><para>12 bits for a sequence number</para></listitem> + </itemizedlist> + </para> + + <sect2 id="snowflake-functions"> + <title>Functions</title> + + <variablelist> + <varlistentry> + <term> + <function>snowflake_get(raw int8) returns record</function> + <indexterm> + <primary>snowflake_get</primary> + <secondary>function</secondary> + </indexterm> + </term> + + <listitem> + <para> + Returns a record made of the timestamp in milli-seconds, the machine ID + and the sequence number for a single snowflake ID. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-parameters"> + <title>Configuration Parameters</title> + + <variablelist> + <varlistentry> + <term> + <varname>snowflake.machine_id</varname> + <indexterm> + <primary><varname>snowflake.machine_id</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Machine ID assigned to the snowflake IDs used in the sequence. The + default value is <literal>1</literal>. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-examples"> + <title>Examples</title> + + <para> + This is an example of creating a snowflake sequence: + </para> + +<programlisting> +CREATE SEQUENCE snowflake_seq USING snowflake; +</programlisting> + + <para> + Similarly to the default sequence access method, snowflake sequences + can be queried as a table: + </para> + +<programlisting> + =# SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) +=# SELECT to_timestamp(time_ms / 1000), machine, counter + FROM snowflake_get(nextval('snowflake_seq')); + to_timestamp | machine | counter +------------------------+---------+--------- + 2024-04-26 14:28:26+09 | 1 | 3 +(1 row) +</programlisting> + </sect2> + +</sect1> diff --git a/contrib/Makefile b/contrib/Makefile index 952855d9b61b..26ec31734950 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -43,6 +43,7 @@ SUBDIRS = \ pg_walinspect \ postgres_fdw \ seg \ + snowflake \ spi \ tablefunc \ tcn \ diff --git a/contrib/meson.build b/contrib/meson.build index 1ba73ebd67a3..9f11d5b00e44 100644 --- a/contrib/meson.build +++ b/contrib/meson.build @@ -58,6 +58,7 @@ subdir('pg_walinspect') subdir('postgres_fdw') subdir('seg') subdir('sepgsql') +subdir('snowflake') subdir('spi') subdir('sslinfo') # start-scripts doesn't contain build products diff --git a/contrib/snowflake/.gitignore b/contrib/snowflake/.gitignore new file mode 100644 index 000000000000..44d119cfcc24 --- /dev/null +++ b/contrib/snowflake/.gitignore @@ -0,0 +1,3 @@ +# Generated subdirectories +/log/ +/results/ diff --git a/contrib/snowflake/Makefile b/contrib/snowflake/Makefile new file mode 100644 index 000000000000..fa5b48d565d8 --- /dev/null +++ b/contrib/snowflake/Makefile @@ -0,0 +1,19 @@ +# contrib/snowflake/Makefile + +MODULES = snowflake + +EXTENSION = snowflake +DATA = snowflake--1.0.sql + +REGRESS = snowflake + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/snowflake +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/snowflake/expected/snowflake.out b/contrib/snowflake/expected/snowflake.out new file mode 100644 index 000000000000..b7e469bf7384 --- /dev/null +++ b/contrib/snowflake/expected/snowflake.out @@ -0,0 +1,73 @@ +CREATE EXTENSION snowflake; +CREATE SEQUENCE snowflake_seq USING snowflake; +SET snowflake.machine_id = 2000; -- error +ERROR: 2000 is outside the valid range for parameter "snowflake.machine_id" (0 .. 1023) +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(lastval()); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 3 | t +(1 row) + +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + relpersistence +---------------- + u +(1 row) + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; + machine | counter +---------+--------- + 4 | 2 + 4 | 3 + 4 | 4 + 4 | 5 + 4 | 6 + 4 | 7 + 4 | 8 + 4 | 9 + 4 | 10 + 4 | 11 +(10 rows) + +DROP TABLE snowflake_tab; +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; diff --git a/contrib/snowflake/meson.build b/contrib/snowflake/meson.build new file mode 100644 index 000000000000..567669eea790 --- /dev/null +++ b/contrib/snowflake/meson.build @@ -0,0 +1,34 @@ +# Copyright (c) 2024, PostgreSQL Global Development Group + +snowflake_sources = files( + 'snowflake.c', +) + +if host_system == 'windows' + snowflake_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'snowflake', + '--FILEDESC', 'snowflake - sequence access method',]) +endif + +snowflake = shared_module('snowflake', + snowflake_sources, + kwargs: contrib_mod_args, +) +contrib_targets += bloom + +install_data( + 'snowflake.control', + 'snowflake--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'snowflake', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'snowflake', + ], + }, +} diff --git a/contrib/snowflake/snowflake--1.0.sql b/contrib/snowflake/snowflake--1.0.sql new file mode 100644 index 000000000000..bcb9d754f1b4 --- /dev/null +++ b/contrib/snowflake/snowflake--1.0.sql @@ -0,0 +1,21 @@ +/* contrib/snowflake/snowflake--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION snowflake" to load this file. \quit + +CREATE FUNCTION snowflake_sequenceam_handler(internal) + RETURNS sequence_am_handler + AS 'MODULE_PATHNAME' + LANGUAGE C; + +CREATE ACCESS METHOD snowflake + TYPE SEQUENCE HANDLER snowflake_sequenceam_handler; +COMMENT ON ACCESS METHOD snowflake IS 'snowflake sequence access method'; + +CREATE FUNCTION snowflake_get(IN raw int8, + OUT time_ms int8, + OUT machine int4, + OUT counter int4) + RETURNS record + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT diff --git a/contrib/snowflake/snowflake.c b/contrib/snowflake/snowflake.c new file mode 100644 index 000000000000..be4c4039ecd0 --- /dev/null +++ b/contrib/snowflake/snowflake.c @@ -0,0 +1,570 @@ +/*------------------------------------------------------------------------- + * + * snowflake.c + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * contrib/snowflake/snowflake.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <sys/time.h> + +#include "access/generic_xlog.h" +#include "access/sequenceam.h" +#include "access/xact.h" +#include "catalog/storage_xlog.h" +#include "commands/tablecmds.h" +#include "fmgr.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "storage/bufmgr.h" +#include "utils/guc.h" +#include "utils/numeric.h" + +PG_MODULE_MAGIC; + +/* "special area" of a snowflake's buffer page. */ +#define SNOWFLAKE_MAGIC 0x01 + +typedef struct snowflake_magic +{ + uint32 magic; +} snowflake_magic; + +/* ----------------------------------------------------------------------- + * Snowflake ID are 64-bit based, with the following structure: + * - 41 bits for an epoch-based timestamp, in milli-seconds. + * - 10 bits for a machine ID. + * - 12 bits for a sequence counter. + * + * The timestamp can be cut to an offset. The machine ID is controlled + * by a superuser GUC. Sequence properties apply to the sequence counter, + * as the other two are environment-dependent. + * ----------------------------------------------------------------------- + */ + +/* + * Helper routines to convert a snowflake ID from/to an int64. + */ +#define SNOWFLAKE_COUNTER_MASK 0x0000000000000FFF /* 12 bits */ +#define SNOWFLAKE_COUNTER_SHIFT 0 +#define SNOWFLAKE_MACHINE_ID_MASK 0x00000000000003FF /* 10 bits */ +#define SNOWFLAKE_MACHINE_ID_SHIFT 12 /* counter */ +#define SNOWFLAKE_TIMESTAMP_MASK 0x000001FFFFFFFFFF /* 41 bits */ +#define SNOWFLAKE_TIMESTAMP_SHIFT 22 /* machine ID + counter sizes */ + +typedef struct snowflake_id +{ + uint32 count; + uint32 machine; + uint64 time_ms; +} snowflake_id; + +#define snowflake_id_to_int64(id, raw) { \ + raw = (((id).count) & SNOWFLAKE_COUNTER_MASK) << SNOWFLAKE_COUNTER_SHIFT | \ + (((id).machine) & SNOWFLAKE_MACHINE_ID_MASK) << SNOWFLAKE_MACHINE_ID_SHIFT | \ + (((id).time_ms) & SNOWFLAKE_TIMESTAMP_MASK) << SNOWFLAKE_TIMESTAMP_SHIFT; \ +} + +#define int64_to_snowflake_id(raw, id) { \ + (id).count = ((raw) >> SNOWFLAKE_COUNTER_SHIFT) & SNOWFLAKE_COUNTER_MASK; \ + (id).machine = ((raw) >> SNOWFLAKE_MACHINE_ID_SHIFT) & SNOWFLAKE_MACHINE_ID_MASK; \ + (id).time_ms = ((raw) >> SNOWFLAKE_TIMESTAMP_SHIFT) & SNOWFLAKE_TIMESTAMP_MASK; \ +} + +/* + * Format of tuples stored in heap table associated to snowflake sequence. + */ +typedef struct FormData_snowflake_data +{ + /* enough to cover 12 bits of the internal counter */ + int16 count; + bool is_called; +} FormData_snowflake_data; + +typedef FormData_snowflake_data *Form_snowflake_data; + +/* + * Columns of a snowflake sequence relation. + */ +#define SNOWFLAKE_COL_COUNT 1 +#define SNOWFLAKE_COL_CALLED 2 + +#define SNOWFLAKE_COLS 2 + +/* GUC parameter */ +static int snowflake_machine_id = 1; + +PG_FUNCTION_INFO_V1(snowflake_sequenceam_handler); + +/* ----------------------------------------------------------------------- + * Interfaces for relation manipulation. + * ----------------------------------------------------------------------- + */ + +/* + * Initialize snowflake relation's fork with some data. + */ +static void +fill_snowflake_fork(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + snowflake_magic *sm; + OffsetNumber offnum; + GenericXLogState *state = NULL; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(snowflake_magic)); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + sm->magic = SNOWFLAKE_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= HEAP_XMAX_INVALID; + ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber); + + /* + * Initialize before entering in the critical section, as this does + * allocations. + */ + if (forkNum == INIT_FORKNUM) + state = GenericXLogStart(rel); + + START_CRIT_SECTION(); + + MarkBufferDirty(buf); + + offnum = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* + * Init forks have to be logged. These go through generic WAL records + * for simplicity's sake to save from the need of a custom RMGR. + */ + if (forkNum == INIT_FORKNUM) + { + (void) GenericXLogRegisterBuffer(state, buf, GENERIC_XLOG_FULL_IMAGE); + GenericXLogFinish(state); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * Initialize snowflake relation. + * + * This needs to handle both the initial and main forks. + */ +static void +fill_snowflake(Relation rel, HeapTuple tuple) +{ + SMgrRelation srel; + + Assert(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED); + + fill_snowflake_fork(rel, tuple, MAIN_FORKNUM); + + /* init fork */ + srel = smgropen(rel->rd_locator, INVALID_PROC_NUMBER); + smgrcreate(srel, INIT_FORKNUM, false); + log_smgrcreate(&rel->rd_locator, INIT_FORKNUM); + fill_snowflake_fork(rel, tuple, INIT_FORKNUM); + FlushRelationBuffers(rel); + smgrclose(srel); +} + +/* + * Read the current state of a snowflake sequence + * + * Given an opened sequence relation, lock the page buffer and find the tuple. + * + * *buf receives the reference to the pinned-and-ex-locked buffer. + * *seqdatatuple receives the reference to the sequence tuple proper. + * + * Returns value points to the data payload of the tuple. + */ +static Form_snowflake_data +read_snowflake(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + snowflake_magic *sm; + Form_snowflake_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SNOWFLAKE_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_snowflake_data) GETSTRUCT(seqdatatuple); + + return seq; +} + + +/* ------------------------------------------------------------------------ + * Callbacks for the snowflake 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 * +snowflake_sequenceam_get_table_am(void) +{ + return "heap"; +} + +/* + * snowflake_sequenceam_init + * + * Initialize relation of a snowflake sequence. This stores the sequence + * counter in an unlogged relation as timestamps ensure value unicity. + */ +static void +snowflake_sequenceam_init(Relation rel, int64 last_value, bool is_called) +{ + Datum values[SNOWFLAKE_COLS]; + bool nulls[SNOWFLAKE_COLS]; + int16 counter; + TupleDesc tupdesc; + HeapTuple tuple; + List *elts = NIL; + ListCell *lc; + ColumnDef *coldef = NULL; + AlterTableCmd *atcmd; + List *atcmds = NIL; + + /* Adjust last_value, depending on the defaults given */ + counter = ((int16) last_value) & SNOWFLAKE_COUNTER_MASK; + + /* + * Create unlogged relation with its attributes. + */ + coldef = makeColumnDef("count", INT2OID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + + foreach(lc, elts) + { + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + /* + * Switch the relation to be unlogged. This forces a rewrite, but + * the relation is empty so that's OK. + */ + RelationSetNewRelfilenumber(rel, RELPERSISTENCE_UNLOGGED); + + /* And insert its first tuple */ + values[0] = Int16GetDatum(counter); + nulls[0] = false; + values[1] = BoolGetDatum(is_called); + nulls[1] = false; + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, values, nulls); + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_nextval + * + * Return the next value for a snowflake sequence. + */ +static int64 +snowflake_sequenceam_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + Buffer buf; + Form_snowflake_data seq; + HeapTupleData seqdatatuple; + int64 result = 0; + snowflake_id id = {0}; + struct timeval tp; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* + * The logic here is quite simple, increment the counter until its + * threshold is reached and get back to the start. If the threshold + * is reached, wait 1ms to ensure a unique timestamp. There is no + * need to do a retry as the buffer is already locked. + */ + id.count = seq->count; + id.count++; + + if (id.count > (PG_INT16_MAX & SNOWFLAKE_COUNTER_MASK)) + { + /* + * Threshold reached, so wait a bit for force clock to a new + * timestamp. + */ + id.count = 1; + pg_usleep(1000L); /* 1ms */ + } + + /* Compute timestamp, with buffer locked */ + gettimeofday(&tp, NULL); + id.time_ms = (uint64) tp.tv_sec * 1000 + + tp.tv_usec / 1000; + + /* Machine ID */ + id.machine = snowflake_machine_id; + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->count = id.count; + seq->is_called = true; + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + /* Store the last value computed for lastval() */ + snowflake_id_to_int64(id, result); + *last = result; + return result; +} + +/* + * snowflake_sequenceam_setval + * + * Set the sequence value, manipulating only the sequence counter. + */ +static void +snowflake_sequenceam_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* Change the in-buffer tuple */ + START_CRIT_SECTION(); + seq->count = (next & SNOWFLAKE_COUNTER_MASK); + seq->is_called = iscalled; + MarkBufferDirty(buf); + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_get_state + * + * Return the last sequence counter value. + */ +static void +snowflake_sequenceam_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + seq = read_snowflake(rel, &buf, &seqdatatuple); + *last_value = seq->count; + *is_called = seq->is_called; + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_reset + * + * Reset the sequence, coming down to resetting its counter. + */ +static void +snowflake_sequenceam_reset(Relation rel, int64 startv, bool is_called, + bool reset_state) +{ + HeapTupleData seqdatatuple; + HeapTuple tuple; + Form_snowflake_data seq; + Buffer buf; + + /* lock buffer page and read tuple */ + (void) read_snowflake(rel, &buf, &seqdatatuple); + + /* copy the existing tuple */ + tuple = 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 = (Form_snowflake_data) GETSTRUCT(tuple); + seq->count = (startv & SNOWFLAKE_COUNTER_MASK); + seq->is_called = is_called; + + /* create new storage */ + RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence); + + /* insert the modified tuple into the page */ + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_change_persistence + * + * There is nothing to do here, the underneath relation has to remain + * unlogged and is set as such when creating the sequence. + */ +static void +snowflake_sequenceam_change_persistence(Relation rel, char newrelpersistence) +{ + /* Nothing to do here */ +} + +/* ------------------------------------------------------------------------ + * Definition of the snowflake sequence access method. + * ------------------------------------------------------------------------ + */ + +static const SequenceAmRoutine snowflake_sequenceam_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = snowflake_sequenceam_get_table_am, + .init = snowflake_sequenceam_init, + .nextval = snowflake_sequenceam_nextval, + .setval = snowflake_sequenceam_setval, + .get_state = snowflake_sequenceam_get_state, + .reset = snowflake_sequenceam_reset, + .change_persistence = snowflake_sequenceam_change_persistence +}; + +Datum +snowflake_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&snowflake_sequenceam_methods); +} + +/* Utility functions */ + +/* + * snowflake_get + * + * Return a tuple worth of snowflake ID data, in a readable shape. + */ +PG_FUNCTION_INFO_V1(snowflake_get); +Datum +snowflake_get(PG_FUNCTION_ARGS) +{ +#define SNOWFLAKE_GET_COLS 3 + int64 raw = PG_GETARG_INT64(0); + Datum *values; + bool *nulls; + TupleDesc tupdesc; + snowflake_id id; + + /* determine result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + int64_to_snowflake_id(raw, id); + + nulls = palloc0(sizeof(bool) * tupdesc->natts); + values = palloc0(sizeof(Datum) * tupdesc->natts); + + values[0] = Int64GetDatum(id.time_ms); + values[1] = Int32GetDatum(id.machine); + values[2] = Int32GetDatum(id.count); + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Entry point when loading extension. + */ +void +_PG_init(void) +{ + DefineCustomIntVariable("snowflake.machine_id", + "Machine ID to use with snowflake sequence.", + "Default value is 1.", + &snowflake_machine_id, + 1, 0, 1023, /* 10 bits as max */ + PGC_SUSET, + 0, NULL, NULL, NULL); +} diff --git a/contrib/snowflake/snowflake.control b/contrib/snowflake/snowflake.control new file mode 100644 index 000000000000..7b8c6089c25f --- /dev/null +++ b/contrib/snowflake/snowflake.control @@ -0,0 +1,5 @@ +# snowflake extension +comment = 'snowflake - sequence access method' +default_version = '1.0' +module_pathname = '$libdir/snowflake' +relocatable = true diff --git a/contrib/snowflake/sql/snowflake.sql b/contrib/snowflake/sql/snowflake.sql new file mode 100644 index 000000000000..395d166ba4bc --- /dev/null +++ b/contrib/snowflake/sql/snowflake.sql @@ -0,0 +1,29 @@ +CREATE EXTENSION snowflake; + +CREATE SEQUENCE snowflake_seq USING snowflake; + +SET snowflake.machine_id = 2000; -- error +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(lastval()); +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; +DROP TABLE snowflake_tab; + +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; -- 2.47.2 [application/pgp-signature] signature.asc (833B, ../../[email protected]/9-signature.asc) download ^ permalink raw reply [nested|flat] 14+ messages in thread
* Re: Sequence Access Methods, round two @ 2025-02-25 07:25 Michael Paquier <[email protected]> parent: Michael Paquier <[email protected]> 0 siblings, 1 reply; 14+ messages in thread From: Michael Paquier @ 2025-02-25 07:25 UTC (permalink / raw) To: Kirill Reshke <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Peter Smith <[email protected]>; Postgres hackers <[email protected]> On Tue, Feb 18, 2025 at 02:11:04PM +0900, Michael Paquier wrote: > Conflicts caused by ed5e5f071033 in the third patch in sequence.c with > adjustments in the new files seqlocalam.c were required, hence here is > a rebased version labelled v11. Rebased. -- Michael Attachments: [text/x-diff] v12-0001-Remove-FormData_pg_sequence_data-from-init_param.patch (9.3K, ../../[email protected]/2-v12-0001-Remove-FormData_pg_sequence_data-from-init_param.patch) download | inline diff: From 78c467e5bc477b62d70b470c5f5fcc77c7752a08 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:00:45 +0900 Subject: [PATCH v12 1/7] 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/sequence.c index 4b7c5113aabe..576c2ec502db 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 rel, 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); 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 = makeNode(CreateStmt); @@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) /* 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); /* @@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) { case SEQ_COL_LASTVAL: coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(seqdataform.last_value); + value[i - 1] = Int64GetDatumFast(last_value); break; case SEQ_COL_LOG: coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); @@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) ObjectAddress address; Relation rel; HeapTuple seqtuple; + bool reset_state = false; + bool is_called; + int64 last_value; HeapTuple newdatatuple; /* 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 = heap_copytuple(&datatuple); newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); + last_value = newdataform->last_value; + is_called = newdataform->is_called; UnlockReleaseBuffer(buf); /* 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); /* 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 = last_value; + newdataform->is_called = is_called; + if (reset_state) + newdataform->log_cnt = 0; fill_seq_with_data(seqrel, newdatatuple); } @@ -1236,17 +1249,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). Also + * 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 overridden. * * Note: we force a sequence rewrite whenever we change parameters that affect - * 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. Currently, * 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 @@ -1257,7 +1272,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) { @@ -1363,11 +1380,11 @@ init_params(ParseState *pstate, List *options, bool for_identity, } /* - * 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 = 0; + *reset_state = true; /* AS type */ if (as_type != NULL) @@ -1416,7 +1433,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("INCREMENT must not be zero"))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1428,7 +1445,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, { seqform->seqcycle = boolVal(is_cycled->arg); Assert(BoolIsValid(seqform->seqcycle)); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1439,7 +1456,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (max_value != NULL && max_value->arg) { seqform->seqmax = defGetInt64(max_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || max_value != NULL || reset_max_value) { @@ -1455,7 +1472,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmax = -1; /* descending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate maximum value. No need to check INT8 as seqmax is an int64 */ @@ -1471,7 +1488,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (min_value != NULL && min_value->arg) { seqform->seqmin = defGetInt64(min_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || min_value != NULL || reset_min_value) { @@ -1487,7 +1504,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmin = 1; /* ascending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate minimum value. No need to check INT8 as seqmin is an int64 */ @@ -1538,30 +1555,30 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (restart_value != NULL) { if (restart_value->arg != NULL) - seqdataform->last_value = defGetInt64(restart_value); + *last_value = defGetInt64(restart_value); else - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; - seqdataform->log_cnt = 0; + *last_value = seqform->seqstart; + *is_called = false; + *reset_state = true; } else if (isInit) { - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; + *last_value = seqform->seqstart; + *is_called = false; } /* 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))); /* CACHE */ @@ -1573,7 +1590,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("CACHE (%lld) must be greater than zero", (long long) seqform->seqcache))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { -- 2.47.2 [text/x-diff] v12-0002-Integrate-addition-of-attributes-for-sequences-w.patch (11.2K, ../../[email protected]/3-v12-0002-Integrate-addition-of-attributes-for-sequences-w.patch) download | inline diff: From 9be8a972a8ad6c0ad0be6869086ee745b381047e Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 19 Jan 2024 15:00:14 +0900 Subject: [PATCH v12 2/7] 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 0b208f51bdd0..e5848e737149 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2402,6 +2402,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/sequence.c index 576c2ec502db..a902c6ce6f09 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 = NIL; + List *atcmds = 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 = NIL; for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) { ColumnDef *coldef = NULL; @@ -198,7 +200,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) coldef->is_not_null = true; null[i - 1] = false; - stmt->tableElts = lappend(stmt->tableElts, coldef); + elts = lappend(elts, coldef); } stmt->relation = seq->sequence; @@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; + /* + * Initial relation has no attributes, these are added later. + */ + stmt->tableElts = NIL; address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + tupDesc = RelationGetDescr(rel); /* now initialize the sequence's data */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index ce7d115667eb..58dc70aad221 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4616,6 +4616,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 */ @@ -4911,6 +4912,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* Recursion occurs during execution phase */ pass = 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 = 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, @@ -5343,6 +5351,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 = ATExecAddColumn(wqueue, tab, rel, &cmd, cmd->recurse, false, @@ -6502,6 +6511,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 25fe3d580166..0629dc5387c0 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1667,7 +1667,11 @@ ProcessUtilitySlow(ParseState *pstate, break; case T_CreateSeqStmt: + EventTriggerAlterTableStart(parsetree); address = DefineSequence(pstate, (CreateSeqStmt *) parsetree); + /* stashed internally */ + commandCollected = true; + EventTriggerAlterTableEnd(); break; case T_AlterSeqStmt: diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/test/modules/test_ddl_deparse/expected/alter_table.out index 50d0354a3417..ed31059ef584 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 of sequence parent_b_seq NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type ADD COLUMN (and recurse) desc column b of table parent NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint parent_b_not_null on table parent @@ -70,7 +73,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 parent_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 of 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 table parent diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out index 5837ea484e40..310ce5a6baf5 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 of 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 14915f661a89..527c67995a94 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 of 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 of 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 of sequence datatype_table_is_small_seq NOTICE: DDL test: type simple, tag CREATE TABLE NOTICE: DDL test: type simple, tag CREATE INDEX NOTICE: DDL test: type simple, tag CREATE INDEX diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c index 193669f2bc1e..254fdf90c79a 100644 --- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c +++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c @@ -113,6 +113,9 @@ get_altertable_subcmdinfo(PG_FUNCTION_ARGS) case AT_AddColumn: strtype = "ADD COLUMN"; break; + case AT_AddColumnToSequence: + strtype = "ADD COLUMN TO SEQUENCE"; + break; case AT_AddColumnToView: strtype = "ADD COLUMN TO VIEW"; break; -- 2.47.2 [text/x-diff] v12-0003-Refactor-code-for-in-core-local-sequences.patch (54.5K, ../../[email protected]/4-v12-0003-Refactor-code-for-in-core-local-sequences.patch) download | inline diff: From 6e374358494d3f5fd6bddec67255eb4fadf53d42 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Thu, 25 Apr 2024 16:49:47 +0900 Subject: [PATCH v12 3/7] Refactor code for in-core "local" sequences This commit restructures the code of in-core sequences into a new set of files: - seqdesc.c is renamed to seqlocaldesc.c. - seqlocalxlog.c to the code in the WAL replay logic. - seqlocalam.c to store a set of routines called from sequence.c, finishing the separation between the main sequence logic and the in-core sequences. - seqlocalam.h to store the AM-specific structures and routine related to the in-core sequences. WAL records are renamed to "SequenceLocal" with structures, variables and file structures mapping to that. --- src/include/access/rmgrlist.h | 2 +- src/include/access/seqlocalam.h | 58 ++ src/backend/access/rmgrdesc/Makefile | 2 +- src/backend/access/rmgrdesc/meson.build | 2 +- .../rmgrdesc/{seqdesc.c => seqlocaldesc.c} | 20 +- src/backend/access/sequence/Makefile | 2 +- src/backend/access/sequence/meson.build | 2 + src/backend/access/sequence/seqlocalam.c | 634 ++++++++++++++++++ src/backend/access/sequence/seqlocalxlog.c | 82 +++ src/backend/access/transam/rmgr.c | 1 + src/backend/commands/sequence.c | 634 +----------------- src/bin/pg_waldump/.gitignore | 2 +- src/bin/pg_waldump/rmgrdesc.c | 1 + src/bin/pg_waldump/t/001_basic.pl | 2 +- 14 files changed, 822 insertions(+), 622 deletions(-) create mode 100644 src/include/access/seqlocalam.h rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%) create mode 100644 src/backend/access/sequence/seqlocalam.c create mode 100644 src/backend/access/sequence/seqlocalxlog.c diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h index 8e7fc9db8778..b942b25350bc 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, btree_identify, btree_xlog PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL, hash_mask, NULL) PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, 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, NULL, seq_mask, NULL) +PG_RMGR(RM_SEQ_LOCAL_ID, "SequenceLocal", seq_local_redo, seq_local_desc, seq_local_identify, NULL, NULL, seq_local_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, NULL, brin_mask, NULL) PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL, NULL, NULL) diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h new file mode 100644 index 000000000000..225fb9a2cbeb --- /dev/null +++ b/src/include/access/seqlocalam.h @@ -0,0 +1,58 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.h + * Local sequence access method. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/access/seqlocalam.h + * + *------------------------------------------------------------------------- + */ +#ifndef SEQLOCALAM_H +#define SEQLOCALAM_H + +#include "access/xlogreader.h" +#include "storage/relfilelocator.h" +#include "utils/rel.h" + +/* XLOG stuff */ +#define XLOG_SEQ_LOCAL_LOG 0x00 + +typedef struct xl_seq_local_rec +{ + RelFileLocator locator; + /* SEQUENCE TUPLE DATA FOLLOWS AT THE END */ +} xl_seq_local_rec; + +/* + * The "special area" of a local sequence's buffer page looks like this. + */ +#define SEQ_LOCAL_MAGIC 0x1717 + +typedef struct seq_local_magic +{ + uint32 magic; +} seq_local_magic; + +extern void seq_local_redo(XLogReaderState *record); +extern void seq_local_desc(StringInfo buf, XLogReaderState *record); +extern const char *seq_local_identify(uint8 info); +extern void seq_local_mask(char *page, BlockNumber blkno); + +/* access routines */ +extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last); +extern const char *seq_local_get_table_am(void); +extern void seq_local_init(Relation rel, int64 last_value, bool is_called); +extern void seq_local_setval(Relation rel, int64 next, bool iscalled); +extern void seq_local_reset(Relation rel, int64 startv, bool is_called, + bool reset_state); +extern void seq_local_get_state(Relation rel, int64 *last_value, + bool *is_called); +extern void seq_local_change_persistence(Relation rel, + char newrelpersistence); + +#endif /* SEQLOCALAM_H */ diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile index cd95eec37f14..e5900ed77af5 100644 --- a/src/backend/access/rmgrdesc/Makefile +++ b/src/backend/access/rmgrdesc/Makefile @@ -24,7 +24,7 @@ OBJS = \ relmapdesc.o \ replorigindesc.o \ rmgrdesc_utils.o \ - seqdesc.o \ + seqlocaldesc.o \ smgrdesc.o \ spgdesc.o \ standbydesc.o \ diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build index 96c98e800c22..6b3b3e1e44c1 100644 --- a/src/backend/access/rmgrdesc/meson.build +++ b/src/backend/access/rmgrdesc/meson.build @@ -17,7 +17,7 @@ rmgr_desc_sources = files( 'relmapdesc.c', 'replorigindesc.c', 'rmgrdesc_utils.c', - 'seqdesc.c', + 'seqlocaldesc.c', 'smgrdesc.c', 'spgdesc.c', 'standbydesc.c', diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/seqlocaldesc.c similarity index 64% rename from src/backend/access/rmgrdesc/seqdesc.c rename to src/backend/access/rmgrdesc/seqlocaldesc.c index 0d289d77fcf7..6ffbcb2c4735 100644 --- a/src/backend/access/rmgrdesc/seqdesc.c +++ b/src/backend/access/rmgrdesc/seqlocaldesc.c @@ -1,44 +1,44 @@ /*------------------------------------------------------------------------- * - * seqdesc.c - * rmgr descriptor routines for commands/sequence.c + * seqlocaldesc.c + * rmgr descriptor routines for sequence/seqlocal.c * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * src/backend/access/rmgrdesc/seqdesc.c + * src/backend/access/rmgrdesc/seqlocaldesc.c * *------------------------------------------------------------------------- */ #include "postgres.h" -#include "commands/sequence.h" +#include "access/seqlocalam.h" void -seq_desc(StringInfo buf, XLogReaderState *record) +seq_local_desc(StringInfo buf, XLogReaderState *record) { char *rec = XLogRecGetData(record); uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - xl_seq_rec *xlrec = (xl_seq_rec *) rec; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) rec; - if (info == XLOG_SEQ_LOG) + if (info == XLOG_SEQ_LOCAL_LOG) appendStringInfo(buf, "rel %u/%u/%u", xlrec->locator.spcOid, xlrec->locator.dbOid, xlrec->locator.relNumber); } const char * -seq_identify(uint8 info) +seq_local_identify(uint8 info) { const char *id = NULL; switch (info & ~XLR_INFO_MASK) { - case XLOG_SEQ_LOG: - id = "LOG"; + case XLOG_SEQ_LOCAL_LOG: + id = "SEQ_LOCAL_LOG"; break; } diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile index 9f9d31f5425a..a15ceec1c0a0 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index ec9ab9b7e9db..8bc0e95e68c0 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -1,5 +1,7 @@ # Copyright (c) 2022-2025, PostgreSQL Global Development Group backend_sources += files( + 'seqlocalam.c', + 'seqlocalxlog.c', 'sequence.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c new file mode 100644 index 000000000000..bc735ebb0838 --- /dev/null +++ b/src/backend/access/sequence/seqlocalam.c @@ -0,0 +1,634 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.c + * Local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalam.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/multixact.h" +#include "access/seqlocalam.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 SEQ_LOCAL_LOG_VALS 32 + +/* Format of tuples stored in heap table associated to local sequences */ +typedef struct FormData_pg_seq_local_data +{ + int64 last_value; + int64 log_cnt; + bool is_called; +} FormData_pg_seq_local_data; + +typedef FormData_pg_seq_local_data *Form_pg_seq_local_data; + +/* + * Columns of a local sequence relation + */ +#define SEQ_LOCAL_COL_LASTVAL 1 +#define SEQ_LOCAL_COL_LOG 2 +#define SEQ_LOCAL_COL_CALLED 3 + +#define SEQ_LOCAL_COL_FIRSTCOL SEQ_LOCAL_COL_LASTVAL +#define SEQ_LOCAL_COL_LASTCOL SEQ_LOCAL_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_LOCAL_LOG_VALS 32 + +static Form_pg_seq_local_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 tuple + * + * *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_seq_local_data +read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + seq_local_magic *sm; + Form_pg_seq_local_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SEQ_LOCAL_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_pg_seq_local_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 == RELPERSISTENCE_UNLOGGED) + { + SMgrRelation srel; + + srel = 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 content + */ +static void +fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + seq_local_magic *sm; + OffsetNumber offnum; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + sm->magic = SEQ_LOCAL_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + + XLogRegisterData(&xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData(tuple->t_data, tuple->t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_nextval() + * + * Allocate a new value for a local sequence, based on the sequence + * configuration. + */ +int64 +seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + int64 result; + int64 fetch; + int64 next; + int64 rescnt = 0; + int64 log; + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + Page page; + bool logit = false; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + page = BufferGetPage(buf); + + *last = next = result = seq->last_value; + fetch = cache; + log = 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_LOCAL_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 = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = true; + } + else + { + XLogRecPtr redoptr = GetRedoRecPtr(); + + if (PageGetLSN(page) <= redoptr) + { + /* last update of seq was before checkpoint */ + fetch = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = 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 >= 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 = minv; + } + else + next += incby; + } + else + { + /* descending sequence */ + if ((minv < 0 && next < minv - incby) || + (minv >= 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 = maxv; + } + else + next += incby; + } + fetch--; + if (rescnt < cache) + { + log--; + rescnt++; + *last = next; + if (rescnt == 1) /* if it's first result - */ + result = next; /* it's what to return */ + } + } + + log -= fetch; /* adjust for any unfetched numbers */ + Assert(log >= 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_seq_local_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 = next; + seq->is_called = true; + seq->log_cnt = 0; + + xlrec.locator = rel->rd_locator; + + XLogRegisterData(&xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData(seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + /* Now update sequence tuple to the intended final state */ + seq->last_value = *last; /* last fetched number */ + seq->is_called = true; + seq->log_cnt = log; /* how much is logged */ + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + return result; +} + +/* + * seq_local_get_table_am() + * + * Return the table access method used by this sequence. + */ +const char * +seq_local_get_table_am(void) +{ + return "heap"; +} + +/* + * seq_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 + * the information guessed from pg_sequences. This is the first tuple + * inserted after the relation has been created, filling in its heap + * table. + */ +void +seq_local_init(Relation rel, int64 last_value, bool is_called) +{ + Datum value[SEQ_LOCAL_COL_LASTCOL]; + bool null[SEQ_LOCAL_COL_LASTCOL]; + List *elts = NIL; + List *atcmds = NIL; + ListCell *lc; + TupleDesc tupdesc; + HeapTuple tuple; + + /* + * Create relation (and fill value[] and null[] for the initial tuple). + */ + for (int i = SEQ_LOCAL_COL_FIRSTCOL; i <= SEQ_LOCAL_COL_LASTCOL; i++) + { + ColumnDef *coldef = NULL; + + switch (i) + { + case SEQ_LOCAL_COL_LASTVAL: + coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatumFast(last_value); + break; + case SEQ_LOCAL_COL_LOG: + coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatum(0); + break; + case SEQ_LOCAL_COL_CALLED: + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + value[i - 1] = BoolGetDatum(is_called); + break; + } + + coldef->is_not_null = true; + null[i - 1] = false; + elts = lappend(elts, coldef); + } + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, value, null); + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_setval() + * + * Callback for setval(). + */ +void +seq_local_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->last_value = next; /* last fetched number */ + seq->is_called = iscalled; + seq->log_cnt = 0; + + MarkBufferDirty(buf); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel)) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + Page page = BufferGetPage(buf); + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + XLogRegisterData(&xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData(seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_reset() + * + * Perform a hard reset on the local sequence, rewriting its heap data + * entirely. + */ +void +seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) +{ + Form_pg_seq_local_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 = 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 = (Form_pg_seq_local_data) GETSTRUCT(tuple); + seq->last_value = startv; + seq->is_called = is_called; + if (reset_state) + seq->log_cnt = 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 == InvalidTransactionId); + Assert(rel->rd_rel->relminmxid == InvalidMultiXactId); + + /* + * Insert the modified tuple into the new storage file. + */ + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_get_state() + * + * Retrieve the state of a local sequence. + */ +void +seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + *last_value = seq->last_value; + *is_called = seq->is_called; + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_change_persistence() + * + * Persistence change for the local sequence Relation. + */ +void +seq_local_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/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c new file mode 100644 index 000000000000..db0ad969dbf2 --- /dev/null +++ b/src/backend/access/sequence/seqlocalxlog.c @@ -0,0 +1,82 @@ +/*------------------------------------------------------------------------- + * + * seqlocalxlog.c + * WAL replay logic for local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalxlog.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/bufmask.h" +#include "access/seqlocalam.h" +#include "access/xlogutils.h" +#include "storage/block.h" + +/* + * Mask a Sequence page before performing consistency checks on it. + */ +void +seq_local_mask(char *page, BlockNumber blkno) +{ + mask_page_lsn_and_checksum(page); + + mask_unused_space(page); +} + +void +seq_local_redo(XLogReaderState *record) +{ + XLogRecPtr lsn = record->EndRecPtr; + uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; + Buffer buffer; + Page page; + Page localpage; + char *item; + Size itemsz; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record); + seq_local_magic *sm; + + if (info != XLOG_SEQ_LOCAL_LOG) + elog(PANIC, "seq_redo: unknown op code %u", info); + + buffer = XLogInitBufferForRedo(record, 0); + page = (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 = (Page) palloc(BufferGetPageSize(buffer)); + + PageInit(localpage, BufferGetPageSize(buffer), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(localpage); + sm->magic = SEQ_LOCAL_MAGIC; + + item = (char *) xlrec + sizeof(xl_seq_local_rec); + itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec); + + if (PageAddItem(localpage, (Item) item, itemsz, + FirstOffsetNumber, false, false) == InvalidOffsetNumber) + elog(PANIC, "seq_local_redo: failed to add item to page"); + + PageSetLSN(localpage, lsn); + + memcpy(page, localpage, BufferGetPageSize(buffer)); + MarkBufferDirty(buffer); + UnlockReleaseBuffer(buffer); + + pfree(localpage); +} diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c index 1b7499726eb0..cc92268937b1 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/seqlocalam.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/sequence.c index a902c6ce6f09..b7ed47d92801 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,6 +16,7 @@ #include "access/bufmask.h" #include "access/htup_details.h" +#include "access/seqlocalam.h" #include "access/multixact.h" #include "access/relation.h" #include "access/sequence.h" @@ -50,23 +51,6 @@ #include "utils/varlena.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 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 current * session. This is needed to hold onto nextval/currval state. (We can't @@ -96,13 +80,9 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */ */ static SeqTableData *last_used_seq = NULL; -static void fill_seq_with_data(Relation rel, HeapTuple tuple); -static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber 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_identity, 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 = NIL; - List *atcmds = NIL; - ListCell *lc; Datum pgs_values[Natts_pg_sequence]; bool pgs_nulls[Natts_pg_sequence]; - int i; /* * 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); - /* - * Create relation (and fill value[] and null[] for the tuple) - */ - for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) - { - ColumnDef *coldef = NULL; - - switch (i) - { - case SEQ_COL_LASTVAL: - coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(last_value); - break; - case SEQ_COL_LOG: - coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatum((int64) 0); - break; - case SEQ_COL_CALLED: - coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); - value[i - 1] = BoolGetDatum(false); - break; - } - - coldef->is_not_null = true; - null[i - 1] = false; - - elts = lappend(elts, coldef); - } - stmt->relation = seq->sequence; stmt->inhRelations = NIL; stmt->constraints = NIL; @@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) */ stmt->tableElts = NIL; + /* + * Initial relation has no attributes, these can be added later via the + * "init" AM callback. + */ + stmt->tableElts = NIL; + address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); - /* Add all the attributes to the sequence */ - foreach(lc, elts) - { - AlterTableCmd *atcmd; - - atcmd = makeNode(AlterTableCmd); - atcmd->subtype = AT_AddColumnToSequence; - atcmd->def = (Node *) lfirst(lc); - atcmds = lappend(atcmds, atcmd); - } - - /* - * No recursion needed. Note that EventTriggerAlterTableStart() should - * have been called. - */ - AlterTableInternal(RelationGetRelid(rel), atcmds, false); - CommandCounterIncrement(); - - tupDesc = RelationGetDescr(rel); - - /* now initialize the sequence's data */ - tuple = heap_form_tuple(tupDesc, value, null); - fill_seq_with_data(rel, tuple); + /* now initialize the sequence table structure and its data */ + seq_local_init(rel, last_value, is_called); /* 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); pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid)); if (!HeapTupleIsValid(pgstuple)) @@ -315,40 +240,8 @@ ResetSequence(Oid seq_relid) startv = pgsform->seqstart; ReleaseSysCache(pgstuple); - /* - * Copy the existing sequence tuple. - */ - tuple = 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 = (Form_pg_sequence_data) GETSTRUCT(tuple); - seq->last_value = startv; - seq->is_called = false; - seq->log_cnt = 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 == InvalidTransactionId); - Assert(seq_rel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - fill_seq_with_data(seq_rel, tuple); + /* Sequence state is forcibly reset here. */ + seq_local_reset(seq_rel, startv, false, true); /* 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); } -/* - * 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 == RELPERSISTENCE_UNLOGGED) - { - SMgrRelation srel; - - srel = 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 content - */ -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 = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, - EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); - Assert(BufferGetBlockNumber(buf) == 0); - - page = BufferGetPage(buf); - - PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(page); - sm->magic = SEQ_MAGIC; - - /* Now insert sequence tuple */ - - /* - * Since VACUUM does not process sequences, we have to force the tuple to - * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, - InvalidOffsetNumber, false, false); - if (offnum != FirstOffsetNumber) - elog(ERROR, "failed to add sequence tuple to page"); - - /* XLOG stuff */ - if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = rel->rd_locator; - - XLogRegisterData(&xlrec, sizeof(xl_seq_rec)); - XLogRegisterData(tuple->t_data, tuple->t_len); - - recptr = 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 = false; bool is_called; int64 last_value; - HeapTuple newdatatuple; /* Open and lock sequence, and check for ownership along the way. */ relid = RangeVarGetRelidExtended(stmt->sequence, @@ -507,16 +296,8 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) seqform = (Form_pg_sequence) GETSTRUCT(seqtuple); - /* 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 = heap_copytuple(&datatuple); - newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); - last_value = newdataform->last_value; - is_called = newdataform->is_called; - - UnlockReleaseBuffer(buf); + /* Read sequence data */ + seq_local_get_state(seqrel, &last_value, &is_called); /* 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(); - /* - * 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 == InvalidTransactionId); - Assert(seqrel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - newdataform->last_value = last_value; - newdataform->is_called = is_called; - if (reset_state) - newdataform->log_cnt = 0; - fill_seq_with_data(seqrel, newdatatuple); + seq_local_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) { SeqTable elm; Relation seqrel; - Buffer buf; - HeapTupleData seqdatatuple; /* * ALTER SEQUENCE acquires this lock earlier. If we're processing an @@ -596,10 +353,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - (void) read_seq_tuple(seqrel, &buf, &seqdatatuple); - RelationSetNewRelfilenumber(seqrel, newrelpersistence); - fill_seq_with_data(seqrel, &seqdatatuple); - UnlockReleaseBuffer(buf); + seq_local_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -662,24 +416,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 = 0; + int64 result; bool cycle; - bool logit = false; /* open and lock sequence */ init_sequence(relid, &elm, &seqrel); @@ -724,105 +469,9 @@ nextval_internal(Oid relid, bool check_permissions) cycle = pgsform->seqcycle; ReleaseSysCache(pgstuple); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - page = BufferGetPage(buf); - - last = next = result = seq->last_value; - fetch = cache; - log = 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 = log = fetch + SEQ_LOG_VALS; - logit = true; - } - else - { - XLogRecPtr redoptr = GetRedoRecPtr(); - - if (PageGetLSN(page) <= redoptr) - { - /* last update of seq was before checkpoint */ - fetch = log = fetch + SEQ_LOG_VALS; - logit = 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 >= 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 = minv; - } - else - next += incby; - } - else - { - /* descending sequence */ - if ((minv < 0 && next < minv - incby) || - (minv >= 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 = maxv; - } - else - next += incby; - } - fetch--; - if (rescnt < cache) - { - log--; - rescnt++; - last = next; - if (rescnt == 1) /* if it's first result - */ - result = next; /* it's what to return */ - } - } - - log -= fetch; /* adjust for any unfetched numbers */ - Assert(log >= 0); + /* retrieve next value from the access method */ + result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -832,69 +481,6 @@ nextval_internal(Oid relid, bool check_permissions) last_used_seq = elm; - /* - * 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 = next; - seq->is_called = true; - seq->log_cnt = 0; - - xlrec.locator = seqrel->rd_locator; - - XLogRegisterData(&xlrec, sizeof(xl_seq_rec)); - XLogRegisterData(seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - /* Now update sequence tuple to the intended final state */ - seq->last_value = last; /* last fetched number */ - seq->is_called = true; - seq->log_cnt = log; /* how much is logged */ - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); - sequence_close(seqrel, NoLock); return result; @@ -984,9 +570,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, @@ -1020,9 +603,6 @@ do_setval(Oid relid, int64 next, bool iscalled) */ PreventCommandIfParallelMode("setval()"); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - if ((next < minv) || (next > maxv)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), @@ -1044,37 +624,8 @@ do_setval(Oid relid, int64 next, bool iscalled) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - /* ready to change the on-disk (or really, in-buffer) tuple */ - START_CRIT_SECTION(); - - seq->last_value = next; /* last fetched number */ - seq->is_called = iscalled; - seq->log_cnt = 0; - - MarkBufferDirty(buf); - - /* XLOG stuff */ - if (RelationNeedsWAL(seqrel)) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - Page page = BufferGetPage(buf); - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = seqrel->rd_locator; - XLogRegisterData(&xlrec, sizeof(xl_seq_rec)); - XLogRegisterData(seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); + /* Call the access method callback */ + seq_local_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1215,62 +766,6 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) } -/* - * Given an opened sequence relation, lock the page buffer and find the tuple - * - * *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 = ReadBuffer(rel, 0); - LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); - - page = BufferGetPage(*buf); - sm = (sequence_magic *) PageGetSpecialPointer(page); - - if (sm->magic != SEQ_MAGIC) - elog(ERROR, "bad magic number in sequence \"%s\": %08X", - RelationGetRelationName(rel), sm->magic); - - lp = PageGetItemId(page, FirstOffsetNumber); - Assert(ItemIdIsNormal(lp)); - - /* Note we currently only bother to set these two fields of *seqdatatuple */ - seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); - seqdatatuple->t_len = 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) != InvalidTransactionId) - { - HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); - seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; - seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; - MarkBufferDirtyHint(*buf, true); - } - - seq = (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 @@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; + bool is_called; + int64 last_value; - seq = read_seq_tuple(seqrel, &buf, &seqtuple); + seq_local_get_state(seqrel, &last_value, &is_called); - values[0] = Int64GetDatum(seq->last_value); - values[1] = BoolGetDatum(seq->is_called); - - UnlockReleaseBuffer(buf); + values[0] = Int64GetDatum(last_value); + values[1] = BoolGetDatum(is_called); } else memset(isnull, true, sizeof(isnull)); @@ -1908,17 +1400,9 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; - - seq = read_seq_tuple(seqrel, &buf, &seqtuple); - - is_called = seq->is_called; - result = seq->last_value; - - UnlockReleaseBuffer(buf); + seq_local_get_state(seqrel, &result, &is_called); } + sequence_close(seqrel, NoLock); if (is_called) @@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - -void -seq_redo(XLogReaderState *record) -{ - XLogRecPtr lsn = record->EndRecPtr; - uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - Buffer buffer; - Page page; - Page localpage; - char *item; - Size itemsz; - xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record); - sequence_magic *sm; - - if (info != XLOG_SEQ_LOG) - elog(PANIC, "seq_redo: unknown op code %u", info); - - buffer = XLogInitBufferForRedo(record, 0); - page = (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 = (Page) palloc(BufferGetPageSize(buffer)); - - PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(localpage); - sm->magic = SEQ_MAGIC; - - item = (char *) xlrec + sizeof(xl_seq_rec); - itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec); - - if (PageAddItem(localpage, (Item) item, itemsz, - FirstOffsetNumber, false, false) == 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. */ @@ -1992,14 +1425,3 @@ ResetSequenceCaches(void) last_used_seq = 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 ec51f41c767e..8d1195de2637 100644 --- a/src/bin/pg_waldump/.gitignore +++ b/src/bin/pg_waldump/.gitignore @@ -10,13 +10,13 @@ /gistdesc.c /hashdesc.c /heapdesc.c +/seqlocaldesc.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 6b8c17bb4c4e..2fccc7a4e2bd 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/seqlocalam.h" #include "access/multixact.h" #include "access/nbtxlog.h" #include "access/rmgr.h" diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl index 5c8fea275bb5..56a6e93a9a77 100644 --- a/src/bin/pg_waldump/t/001_basic.pl +++ b/src/bin/pg_waldump/t/001_basic.pl @@ -67,7 +67,7 @@ Btree Hash Gin Gist -Sequence +SequenceLocal SPGist BRIN CommitTs -- 2.47.2 [text/x-diff] v12-0004-Sequence-access-methods-backend-support.patch (64.1K, ../../[email protected]/5-v12-0004-Sequence-access-methods-backend-support.patch) download | inline diff: From 706db43ae51162c629594ee47059c3426bbb5c09 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Wed, 4 Dec 2024 08:09:46 +0900 Subject: [PATCH v12 4/7] Sequence access methods - backend support The "seqlocal" sequence AM is now plugged in as a handler in the relcache, and a set of callbacks in sequenceam.h. --- src/include/access/seqlocalam.h | 15 -- src/include/access/sequenceam.h | 181 ++++++++++++++++++ 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/meson.build | 1 + src/backend/access/sequence/seqlocalam.c | 41 +++- 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 | 23 +-- 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/psql/describe.c | 2 + src/bin/psql/tab-complete.in.c | 4 +- src/test/regress/expected/create_am.out | 55 ++++-- src/test/regress/expected/opr_sanity.out | 12 ++ src/test/regress/expected/psql.out | 96 +++++----- src/test/regress/expected/type_sanity.out | 12 +- src/test/regress/sql/create_am.sql | 24 ++- src/test/regress/sql/opr_sanity.sql | 10 + src/test/regress/sql/type_sanity.sql | 12 +- src/tools/pgindent/typedefs.list | 5 +- 40 files changed, 695 insertions(+), 182 deletions(-) create mode 100644 src/include/access/sequenceam.h create mode 100644 src/backend/access/sequence/sequenceamapi.c diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h index 225fb9a2cbeb..21936511ac2b 100644 --- a/src/include/access/seqlocalam.h +++ b/src/include/access/seqlocalam.h @@ -15,7 +15,6 @@ #include "access/xlogreader.h" #include "storage/relfilelocator.h" -#include "utils/rel.h" /* XLOG stuff */ #define XLOG_SEQ_LOCAL_LOG 0x00 @@ -41,18 +40,4 @@ extern void seq_local_desc(StringInfo buf, XLogReaderState *record); extern const char *seq_local_identify(uint8 info); extern void seq_local_mask(char *page, BlockNumber blkno); -/* access routines */ -extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last); -extern const char *seq_local_get_table_am(void); -extern void seq_local_init(Relation rel, int64 last_value, bool is_called); -extern void seq_local_setval(Relation rel, int64 next, bool iscalled); -extern void seq_local_reset(Relation rel, int64 startv, bool is_called, - bool reset_state); -extern void seq_local_get_state(Relation rel, int64 *last_value, - bool *is_called); -extern void seq_local_change_persistence(Relation rel, - char newrelpersistence); - #endif /* SEQLOCALAM_H */ diff --git a/src/include/access/sequenceam.h b/src/include/access/sequenceam.h new file mode 100644 index 000000000000..ac48c8b468be --- /dev/null +++ b/src/include/access/sequenceam.h @@ -0,0 +1,181 @@ +/*------------------------------------------------------------------------- + * + * sequenceam.h + * POSTGRES sequence access method definitions. + * + * + * Portions Copyright (c) 1996-2024, 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 "seqlocal" + +/* 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 value + * allocated. The result returned is the next value of the sequence computed. + */ +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); + +#endif /* SEQUENCEAM_H */ diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat index 26d15928a155..8f076fcec958 100644 --- a/src/include/catalog/pg_am.dat +++ b/src/include/catalog/pg_am.dat @@ -15,6 +15,9 @@ { oid => '2', oid_symbol => 'HEAP_TABLE_AM_OID', descr => 'heap table access method', amname => 'heap', amhandler => 'heap_tableam_handler', amtype => 't' }, +{ oid => '8051', oid_symbol => 'LOCAL_SEQUENCE_AM_OID', + descr => 'local sequence access method', + amname => 'seqlocal', amhandler => 'seq_local_sequenceam_handler', amtype => 's' }, { oid => '403', oid_symbol => 'BTREE_AM_OID', descr => 'b-tree index access method', amname => 'btree', amhandler => 'bthandler', amtype => 'i' }, diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h index 6e98a0930c27..080bea5031a9 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 */ #endif /* EXPOSE_TO_CLIENT_CODE */ diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index f0d612ca4877..f217c8d8fb07 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) == RELKIND_TOASTVALUE || \ (relkind) == RELKIND_MATVIEW) +/* + * Relation kinds with a sequence access method (rd_sequenceam). + */ +#define RELKIND_HAS_SEQUENCE_AM(relkind) \ + ((relkind) == RELKIND_SEQUENCE) + extern int errdetail_relkind_not_supported(char relkind); #endif /* EXPOSE_TO_CLIENT_CODE */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index af9546de23df..3e9cddc4bdd7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -913,6 +913,12 @@ prorettype => 'table_am_handler', proargtypes => 'internal', prosrc => 'heap_tableam_handler' }, +# Sequence access method handlers +{ oid => '8209', descr => 'local sequence access method handler', + proname => 'seq_local_sequenceam_handler', provolatile => 'v', + prorettype => 'sequence_am_handler', proargtypes => 'internal', + prosrc => 'seq_local_sequenceam_handler' }, + # Index access method handlers { oid => '330', descr => 'btree index access method handler', proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler', @@ -7803,6 +7809,13 @@ { oid => '327', descr => 'I/O', proname => 'index_am_handler_out', prorettype => 'cstring', proargtypes => 'index_am_handler', prosrc => 'index_am_handler_out' }, +{ oid => '8207', descr => 'I/O', + proname => 'sequence_am_handler_in', proisstrict => 'f', + prorettype => 'sequence_am_handler', proargtypes => 'cstring', + prosrc => 'sequence_am_handler_in' }, +{ oid => '8208', descr => 'I/O', + proname => 'sequence_am_handler_out', prorettype => 'cstring', + proargtypes => 'sequence_am_handler', prosrc => 'sequence_am_handler_out' }, { oid => '3311', descr => 'I/O', proname => 'tsm_handler_in', proisstrict => 'f', prorettype => 'tsm_handler', proargtypes => 'cstring', prosrc => 'tsm_handler_in' }, diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat index 6dca77e0a22f..c42531b25533 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -627,6 +627,12 @@ typcategory => 'P', typinput => 'index_am_handler_in', typoutput => 'index_am_handler_out', typreceive => '-', typsend => '-', typalign => 'i' }, +{ oid => '8210', + descr => 'pseudo-type for the result of a sequence AM handler function', + typname => 'sequence_am_handler', typlen => '4', typbyval => 't', + typtype => 'p', typcategory => 'P', typinput => 'sequence_am_handler_in', + typoutput => 'sequence_am_handler_out', typreceive => '-', typsend => '-', + typalign => 'i' }, { oid => '3310', descr => 'pseudo-type for the result of a tablesample method function', typname => 'tsm_handler', typlen => '4', typbyval => 't', typtype => 'p', diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 6d9348bac804..eb2e344a184f 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); diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index 9ac0b67683d3..7693e9941fc9 100644 --- a/src/include/commands/sequence.h +++ b/src/include/commands/sequence.h @@ -22,35 +22,6 @@ #include "storage/relfilelocator.h" -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); -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 d1ca24dd32f0..b1c4155c9a91 100644 --- a/src/include/nodes/meson.build +++ b/src/include/nodes/meson.build @@ -10,6 +10,7 @@ node_support_input_i = [ 'access/amapi.h', 'access/cmptype.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 e5848e737149..e5bdcf3db9a8 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3207,6 +3207,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; diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 951451a9765f..a346e408ef2b 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, 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 **extra, + 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 db3e504c3d2d..f2155c1195d8 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; + /* + * 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/sequence/Makefile index a15ceec1c0a0..62006165a15f 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = seqlocalam.o seqlocalxlog.o sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o sequenceamapi.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index 8bc0e95e68c0..d82af34d538c 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -4,4 +4,5 @@ backend_sources += files( 'seqlocalam.c', 'seqlocalxlog.c', 'sequence.c', + 'sequenceamapi.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c index bc735ebb0838..946c7f87a686 100644 --- a/src/backend/access/sequence/seqlocalam.c +++ b/src/backend/access/sequence/seqlocalam.c @@ -17,6 +17,7 @@ #include "access/multixact.h" #include "access/seqlocalam.h" +#include "access/sequenceam.h" #include "access/xact.h" #include "access/xloginsert.h" #include "access/xlogutils.h" @@ -24,6 +25,7 @@ #include "commands/tablecmds.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "utils/builtins.h" /* @@ -230,10 +232,10 @@ fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) * Allocate a new value for a local sequence, based on the sequence * configuration. */ -int64 +static int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last) + int64 minv, int64 cache, bool cycle, + int64 *last) { int64 result; int64 fetch; @@ -417,7 +419,7 @@ seq_local_nextval(Relation rel, int64 incby, int64 maxv, * * Return the table access method used by this sequence. */ -const char * +static const char * seq_local_get_table_am(void) { return "heap"; @@ -432,7 +434,7 @@ seq_local_get_table_am(void) * inserted after the relation has been created, filling in its heap * table. */ -void +static void seq_local_init(Relation rel, int64 last_value, bool is_called) { Datum value[SEQ_LOCAL_COL_LASTCOL]; @@ -499,7 +501,7 @@ seq_local_init(Relation rel, int64 last_value, bool is_called) * * Callback for setval(). */ -void +static void seq_local_setval(Relation rel, int64 next, bool iscalled) { Buffer buf; @@ -547,7 +549,7 @@ seq_local_setval(Relation rel, int64 next, bool iscalled) * Perform a hard reset on the local sequence, rewriting its heap data * entirely. */ -void +static void seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) { Form_pg_seq_local_data seq; @@ -600,7 +602,7 @@ seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) * * Retrieve the state of a local sequence. */ -void +static void seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) { Buffer buf; @@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) * * Persistence change for the local sequence Relation. */ -void +static void seq_local_change_persistence(Relation rel, char newrelpersistence) { Buffer buf; @@ -632,3 +634,24 @@ seq_local_change_persistence(Relation rel, char newrelpersistence) fill_seq_with_data(rel, &seqdatatuple); UnlockReleaseBuffer(buf); } + +/* ------------------------------------------------------------------------ + * Definition of the local sequence access method. + * ------------------------------------------------------------------------ + */ +static const SequenceAmRoutine seq_local_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = seq_local_get_table_am, + .init = seq_local_init, + .nextval = seq_local_nextval, + .setval = seq_local_setval, + .reset = seq_local_reset, + .get_state = seq_local_get_state, + .change_persistence = seq_local_change_persistence +}; + +Datum +seq_local_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&seq_local_methods); +} diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/sequence.c index 8b5303553702..f30317d1feae 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 sequences - * (in contrast to other relation types like indexes). + * (in contrast to other relation types like indexes) that are independent + * of individual sequence access methods. * *------------------------------------------------------------------------- */ diff --git a/src/backend/access/sequence/sequenceamapi.c b/src/backend/access/sequence/sequenceamapi.c new file mode 100644 index 000000000000..dd1a60d827a4 --- /dev/null +++ b/src/backend/access/sequence/sequenceamapi.c @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------- + * + * sequenceamapi.c + * general sequence access method routines + * + * Portions Copyright (c) 1996-2024, 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 = 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 = OidFunctionCall0(amhandler); + routine = (SequenceAmRoutine *) DatumGetPointer(datum); + + if (routine == NULL || !IsA(routine, SequenceAmRoutine)) + elog(ERROR, "sequence access method handler %u did not return a SequenceAmRoutine 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 != NULL); + Assert(routine->init != NULL); + Assert(routine->nextval != NULL); + Assert(routine->setval != NULL); + Assert(routine->reset != NULL); + Assert(routine->get_state != NULL); + Assert(routine->change_persistence != 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 = SearchSysCache1(AMOID, + ObjectIdGetDatum(amoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", amoid); + aform = (Form_pg_am) GETSTRUCT(tuple); + Assert(aform->amtype == AMTYPE_SEQUENCE); + amhandleroid = 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 == '\0') + { + GUC_check_errdetail("%s cannot be empty.", + "default_sequence_access_method"); + return false; + } + + if (strlen(*newval) >= 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 != InvalidOid) + { + if (!OidIsValid(get_sequence_am_oid(*newval, true))) + { + /* + * When source == 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 == 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 956f196fc95c..4d06010b9a88 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -1477,9 +1477,13 @@ 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. Partitioned tables may not have an * access method set. + * + * Sequences and tables are created with their access method ID + * given by the caller of this function. */ if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) || - (relkind == RELKIND_PARTITIONED_TABLE && OidIsValid(accessmtd))) + (relkind == RELKIND_PARTITIONED_TABLE && OidIsValid(accessmtd)) || + 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 58ed9d216cc0..e44633d13b60 100644 --- a/src/backend/commands/amcmds.c +++ b/src/backend/commands/amcmds.c @@ -15,6 +15,7 @@ #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); } +/* + * 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 = INDEX_AM_HANDLEROID; break; + case AMTYPE_SEQUENCE: + expectedType = SEQUENCE_AM_HANDLEROID; + break; case AMTYPE_TABLE: expectedType = TABLE_AM_HANDLEROID; break; diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index b7ed47d92801..e88ace9b4a4d 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,10 +16,10 @@ #include "access/bufmask.h" #include "access/htup_details.h" -#include "access/seqlocalam.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 = NIL; stmt->constraints = NIL; stmt->options = NIL; + stmt->accessMethod = seq->accessMethod ? pstrdup(seq->accessMethod) : NULL; stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; @@ -173,7 +174,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) rel = sequence_open(seqoid, AccessExclusiveLock); /* now initialize the sequence table structure and its data */ - seq_local_init(rel, last_value, is_called); + sequence_init(rel, last_value, is_called); /* process OWNED BY if given */ if (owned_by) @@ -241,7 +242,7 @@ ResetSequence(Oid seq_relid) ReleaseSysCache(pgstuple); /* Sequence state is forcibly reset here. */ - seq_local_reset(seq_rel, startv, false, true); + sequence_reset(seq_rel, startv, false, true); /* 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 = (Form_pg_sequence) GETSTRUCT(seqtuple); /* Read sequence data */ - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); /* 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(); - seq_local_reset(seqrel, last_value, is_called, reset_state); + sequence_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -353,7 +354,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - seq_local_change_persistence(seqrel, newrelpersistence); + sequence_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -470,8 +471,8 @@ nextval_internal(Oid relid, bool check_permissions) ReleaseSysCache(pgstuple); /* retrieve next value from the access method */ - result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, - &last); + result = sequence_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -625,7 +626,7 @@ do_setval(Oid relid, int64 next, bool iscalled) GetTopTransactionId(); /* Call the access method callback */ - seq_local_setval(seqrel, next, iscalled); + sequence_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS) bool is_called; int64 last_value; - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); values[0] = Int64GetDatum(last_value); values[1] = BoolGetDatum(is_called); @@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - seq_local_get_state(seqrel, &result, &is_called); + sequence_get_state(seqrel, &result, &is_called); } sequence_close(seqrel, NoLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 58dc70aad221..cd3d18903855 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" @@ -995,14 +996,18 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, } /* - * 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 != NULL) { - Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE); - accessMethodId = get_table_am_oid(stmt->accessMethod, false); + Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE || + RELKIND_HAS_SEQUENCE_AM(relkind)); + if (RELKIND_HAS_SEQUENCE_AM(relkind)) + accessMethodId = get_sequence_am_oid(stmt->accessMethod, false); + else + accessMethodId = get_table_am_oid(stmt->accessMethod, false); } else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE) { @@ -1015,6 +1020,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId)) accessMethodId = get_table_am_oid(default_table_access_method, false); } + else if (RELKIND_HAS_SEQUENCE_AM(relkind)) + { + accessMethodId = get_sequence_am_oid(default_sequence_access_method, false); + } /* * Create the relation. Inherited defaults and CHECK constraints are diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile index 77ddb9ca53f1..64d4dccc936f 100644 --- a/src/backend/nodes/Makefile +++ b/src/backend/nodes/Makefile @@ -48,6 +48,7 @@ node_headers = \ access/amapi.h \ access/cmptype.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 1a657f7e0aea..162ee16dbf6d 100644 --- a/src/backend/nodes/gen_node_support.pl +++ b/src/backend/nodes/gen_node_support.pl @@ -60,6 +60,7 @@ my @all_input_files = qw( access/amapi.h access/cmptype.h access/sdir.h + access/sequenceam.h access/tableam.h access/tsmapi.h commands/event_trigger.h @@ -84,6 +85,7 @@ my @nodetag_only_files = 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 7d99c9355c6a..4144d2dd9b31 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -381,6 +381,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <str> copy_file_name access_method_clause attr_name table_access_method_clause name cursor_name file_name + sequence_access_method_clause cluster_index_specification %type <list> func_name handler_name qual_Op qual_all_Op subquery_Op @@ -4919,23 +4920,26 @@ RefreshMatViewStmt: CreateSeqStmt: CREATE OptTemp SEQUENCE qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); - $4->relpersistence = $2; n->sequence = $4; n->options = $5; + n->accessMethod = $6; n->ownerId = InvalidOid; n->if_not_exists = false; $$ = (Node *) n; } | CREATE OptTemp SEQUENCE IF_P NOT EXISTS qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); $7->relpersistence = $2; n->sequence = $7; n->options = $8; + n->accessMethod = $9; n->ownerId = InvalidOid; n->if_not_exists = true; $$ = (Node *) n; @@ -4972,6 +4976,11 @@ OptParenthesizedSeqOptList: '(' SeqOptList ')' { $$ = $2; } | /*EMPTY*/ { $$ = NIL; } ; +sequence_access_method_clause: + USING name { $$ = $2; } + | /*EMPTY*/ { $$ = NULL; } + ; + SeqOptList: SeqOptElem { $$ = list_make1($1); } | SeqOptList SeqOptElem { $$ = lappend($1, $2); } ; @@ -5975,6 +5984,7 @@ CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name am_type: INDEX { $$ = AMTYPE_INDEX; } + | SEQUENCE { $$ = AMTYPE_SEQUENCE; } | TABLE { $$ = AMTYPE_TABLE; } ; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index abbe1bb45a30..571faec28e98 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" @@ -518,6 +519,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, seqstmt->sequence = makeRangeVar(snamespace, sname, -1); seqstmt->sequence->relpersistence = seqpersistence; seqstmt->options = seqoptions; + seqstmt->accessMethod = NULL; /* * 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/pseudotypes.c index 317a1f2b282f..68f160dda7d3 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/relcache.c index 398114373e9f..c10e77372071 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" @@ -302,6 +304,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); @@ -1196,8 +1199,7 @@ retry: if (relation->rd_rel->relkind == RELKIND_INDEX || relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) RelationInitIndexAccessInfo(relation); - else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || - relation->rd_rel->relkind == RELKIND_SEQUENCE) + else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)) RelationInitTableAccessMethod(relation); else if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { @@ -1206,6 +1208,8 @@ retry: * inherit. */ } + else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind)) + RelationInitSequenceAccessMethod(relation); else Assert(relation->rd_rel->relam == InvalidOid); @@ -1802,17 +1806,9 @@ RelationInitTableAccessMethod(Relation relation) HeapTuple tuple; Form_pg_am aform; - if (relation->rd_rel->relkind == 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 == InvalidOid); - relation->rd_amhandler = 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. @@ -1843,6 +1839,49 @@ RelationInitTableAccessMethod(Relation relation) InitTableAmRoutine(relation); } +/* + * 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 != InvalidOid); + relation->rd_amhandler = GetSequenceAmRoutineId(relation->rd_rel->relam); + + /* + * Now we can fetch the sequence AM's API struct. + */ + relation->rd_sequenceam = GetSequenceAmRoutine(relation->rd_amhandler); + + /* + * From the sequence AM, set its expected table access method. + */ + tableam_name = sequence_get_table_am(relation); + tableam_oid = get_table_am_oid(tableam_name, false); + + tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(tableam_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", + tableam_oid); + aform = (Form_pg_am) GETSTRUCT(tuple); + tableam_handler = aform->amhandler; + ReleaseSysCache(tuple); + + relation->rd_tableam = GetTableAmRoutine(tableam_handler); +} + /* * formrdesc * @@ -3659,14 +3698,17 @@ RelationBuildLocalRelation(const char *relname, rel->rd_rel->relam = accessmtd; /* - * 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); - if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(relkind)) RelationInitTableAccessMethod(rel); + else if (relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); /* * Leave index access method uninitialized, because the pg_index row has @@ -4290,13 +4332,21 @@ RelationCacheInitializePhase3(void) /* Reload tableam data if needed */ if (relation->rd_tableam == NULL && - (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE)) + (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))) { RelationInitTableAccessMethod(relation); Assert(relation->rd_tableam != NULL); restart = true; } + else if (relation->rd_sequenceam == NULL && + relation->rd_rel->relkind == RELKIND_SEQUENCE) + { + RelationInitSequenceAccessMethod(relation); + Assert(relation->rd_sequenceam != NULL); + + restart = true; + } /* Release hold on the relation */ RelationDecrementReferenceCount(relation); @@ -6348,8 +6398,10 @@ load_relcache_init_file(bool shared) nailed_rels++; /* Load table AM data */ - if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || rel->rd_rel->relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind)) RelationInitTableAccessMethod(rel); + else if (rel->rd_rel->relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); Assert(rel->rd_index == NULL); Assert(rel->rd_indextuple == NULL); @@ -6361,6 +6413,7 @@ load_relcache_init_file(bool shared) Assert(rel->rd_supportinfo == NULL); Assert(rel->rd_indoption == NULL); Assert(rel->rd_indcollation == NULL); + Assert(rel->rd_sequenceam == NULL); Assert(rel->rd_opcoptions == NULL); } diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 690bf96ef030..ae918f69bb2c 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" @@ -4257,6 +4258,17 @@ struct config_string ConfigureNamesString[] = check_default_table_access_method, NULL, NULL }, + { + {"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default sequence access method for new sequences."), + 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/utils/misc/postgresql.conf.sample index e771d87da1fb..fbc3f7577c33 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -728,6 +728,7 @@ autovacuum_worker_slots = 16 # autovacuum worker slots to allocate # error #search_path = '"$user", public' # schema names #row_security = on +#default_sequence_access_method = 'seqlocal' #default_table_access_method = 'heap' #default_tablespace = '' # a tablespace name, '' uses the default #default_toast_compression = 'pglz' # 'pglz' or 'lz4' diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index e6cf468ac9e9..b50a3f992eff 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -168,10 +168,12 @@ describeAccessMethods(const char *pattern, bool verbose) "SELECT amname AS \"%s\",\n" " CASE amtype" " WHEN " CppAsString2(AMTYPE_INDEX) " THEN '%s'" + " WHEN " CppAsString2(AMTYPE_SEQUENCE) " THEN '%s'" " WHEN " CppAsString2(AMTYPE_TABLE) " THEN '%s'" " END AS \"%s\"", gettext_noop("Name"), gettext_noop("Index"), + gettext_noop("Sequence"), gettext_noop("Table"), gettext_noop("Type")); diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index 8432be641ac0..4fa37fd4d822 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -2561,7 +2561,7 @@ match_previous_words(int pattern_id, 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 <name> AS */ else if (TailMatches("ALTER", "SEQUENCE", MatchAny, "AS")) COMPLETE_WITH_CS("smallint", "integer", "bigint"); @@ -3601,7 +3601,7 @@ match_previous_words(int pattern_id, 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/expected/create_am.out index c1a951572512..784870e603d1 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; @@ -514,9 +509,12 @@ CREATE TABLE tableam_parted_heapx (a text, b int) PARTITION 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 = 'seqlocal'; 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; @@ -533,18 +531,18 @@ FROM pg_class AS pc LEFT JOIN pg_am AS pa ON (pa.oid = pc.relam) WHERE pc.relname LIKE 'tableam_%_heapx' ORDER BY 3, 1, 2; - relkind | amname | relname ----------+--------+----------------------------- - f | | tableam_fdw_heapx - r | heap2 | tableam_parted_1_heapx - r | heap | tableam_parted_2_heapx - p | | tableam_parted_heapx - S | | tableam_seq_heapx - r | heap2 | tableam_tbl_heapx - r | heap2 | tableam_tblas_heapx - m | heap2 | tableam_tblmv_heapx - r | heap2 | tableam_tblselectinto_heapx - v | | tableam_view_heapx + relkind | amname | relname +---------+----------+----------------------------- + f | | tableam_fdw_heapx + r | heap2 | tableam_parted_1_heapx + r | heap | tableam_parted_2_heapx + p | | tableam_parted_heapx + S | seqlocal | tableam_seq_heapx + r | heap2 | tableam_tbl_heapx + r | heap2 | tableam_tblas_heapx + m | heap2 | tableam_tblmv_heapx + r | heap2 | tableam_tblselectinto_heapx + v | | tableam_view_heapx (10 rows) -- don't want to keep those tables, nor the default @@ -574,3 +572,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_dump support +-- Checks for sequence access methods +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_local_sequenceam_handler; +-- Create and use sequence +CREATE SEQUENCE test_seqam USING local2; +SELECT nextval('test_seqam'::regclass); + nextval +--------- + 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/expected/opr_sanity.out index b673642ad1d7..3dedeff17201 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1940,6 +1940,18 @@ WHERE p1.oid = a1.amhandler AND a1.amtype = 't' AND -----+--------+-----+--------- (0 rows) +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + oid | amname | oid | proname +-----+--------+-----+--------- +(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 6543e90de758..c2a2c1fe0bc2 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -5126,31 +5126,33 @@ Indexes: -- check printing info about access methods \dA List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA * List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA h* List of access methods @@ -5175,32 +5177,34 @@ List of access methods \dA: extra argument "bar" ignored \dA+ - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ * - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ h* List of access methods diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out index 8eff3d10d279..622f360d5b3c 100644 --- a/src/test/regress/expected/type_sanity.out +++ b/src/test/regress/expected/type_sanity.out @@ -507,21 +507,21 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p', 'I') OR -----+--------- (0 rows) --- All tables, indexes, partitioned indexes and matviews should have an --- access method. +-- All tables, indexes, partitioned indexes, matviews and sequences should have +-- an access method. SELECT c1.oid, c1.relname FROM pg_class as c1 -WHERE c1.relkind NOT IN ('S', 'v', 'f', 'c', 'p') and +WHERE c1.relkind NOT IN ('v', 'f', 'c', 'p') and c1.relam = 0; oid | relname -----+--------- (0 rows) --- Conversely, sequences, views, foreign tables, types and partitioned --- tables shouldn't have them. +-- Conversely, views, foreign tables, types and partitioned tables +-- shouldn't have them. SELECT c1.oid, c1.relname FROM pg_class as c1 -WHERE c1.relkind IN ('S', 'v', 'f', 'c', 'p') and +WHERE c1.relkind IN ('v', 'f', 'c', 'p') and c1.relam != 0; oid | relname -----+--------- diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql index 754fe0c694bc..76a91cf8dd68 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 FROM tableam_tbl_heap2; -- CREATE VIEW doesn't support USING CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2; --- 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; @@ -327,9 +324,13 @@ CREATE TABLE tableam_parted_1_heapx PARTITION OF tableam_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; --- 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 = 'seqlocal'; 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; @@ -365,3 +366,16 @@ CREATE FOREIGN TABLE fp PARTITION OF tableam_parted_a_heap2 DEFAULT SERVER x; DROP ACCESS METHOD heap2; -- we intentionally leave the objects created above alive, to verify pg_dump support + +-- Checks for sequence access methods + +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_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 2fb3a8528781..e60eeab33c02 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -1236,6 +1236,16 @@ WHERE p1.oid = a1.amhandler AND a1.amtype = 't' AND OR p1.pronargs != 1 OR p1.proargtypes[0] != 'internal'::regtype); +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + -- **************** pg_amop **************** -- Look for illegal values in pg_amop fields diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql index 303f90955d15..aade05c0e172 100644 --- a/src/test/regress/sql/type_sanity.sql +++ b/src/test/regress/sql/type_sanity.sql @@ -364,18 +364,18 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p', 'I') OR relpersistence NOT IN ('p', 'u', 't') OR relreplident NOT IN ('d', 'n', 'f', 'i'); --- All tables, indexes, partitioned indexes and matviews should have an --- access method. +-- All tables, indexes, partitioned indexes, matviews and sequences should have +-- an access method. SELECT c1.oid, c1.relname FROM pg_class as c1 -WHERE c1.relkind NOT IN ('S', 'v', 'f', 'c', 'p') and +WHERE c1.relkind NOT IN ('v', 'f', 'c', 'p') and c1.relam = 0; --- Conversely, sequences, views, foreign tables, types and partitioned --- tables shouldn't have them. +-- Conversely, views, foreign tables, types and partitioned tables +-- shouldn't have them. SELECT c1.oid, c1.relname FROM pg_class as c1 -WHERE c1.relkind IN ('S', 'v', 'f', 'c', 'p') and +WHERE c1.relkind IN ('v', 'f', 'c', 'p') and c1.relam != 0; -- Indexes and partitioned indexes should have AMs of type 'i'. diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index e3e09a2207e9..8a06eb211af1 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2611,6 +2611,7 @@ SeqScanState SeqTable SeqTableData SeqType +SequenceAmRoutine SequenceItem SerCommitSeqNo SerialControl @@ -3652,6 +3653,7 @@ list_sort_comparator loc_chunk local_relopt local_relopts +local_sequence_magic local_source local_ts_iter local_ts_radix_tree @@ -3940,7 +3942,6 @@ scram_state_enum script_error_callback_arg security_class_t sem_t -sepgsql_context_info_t sequence_magic set_join_pathlist_hook_type set_rel_pathlist_hook_type @@ -4165,6 +4166,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 @@ -4176,7 +4178,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 -- 2.47.2 [text/x-diff] v12-0005-Sequence-access-methods-dump-restore-support.patch (21.7K, ../../[email protected]/6-v12-0005-Sequence-access-methods-dump-restore-support.patch) download | inline diff: From 678d7dbe7e56b4e8c0a7de1a18cb5b7f19e1268b Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Mon, 26 Aug 2024 13:25:19 +0900 Subject: [PATCH v12 5/7] 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 | 47 +++++++++++++++----- 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 | 49 +++++++++++++++++++++ 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, 207 insertions(+), 11 deletions(-) diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index 350cf659c410..8ba0ec05f8b7 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -96,6 +96,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 @@ -187,6 +188,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_backup_archiver.c index 632077113a4a..e264cf721a2c 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -181,6 +181,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) dopt->outputSuperuser = ropt->superuser; dopt->outputCreateDB = ropt->createDB; dopt->outputNoOwner = ropt->noOwner; + dopt->outputNoSequenceAm = ropt->noSequenceAm; dopt->outputNoTableAm = ropt->noTableAm; dopt->outputNoTablespaces = ropt->noTablespace; dopt->disable_triggers = ropt->disable_triggers; @@ -1240,6 +1241,7 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId, newToc->tag = pg_strdup(opts->tag); newToc->namespace = opts->namespace ? pg_strdup(opts->namespace) : NULL; newToc->tablespace = opts->tablespace ? pg_strdup(opts->tablespace) : NULL; + newToc->sequenceam = opts->sequenceam ? pg_strdup(opts->sequenceam) : NULL; newToc->tableam = opts->tableam ? pg_strdup(opts->tableam) : NULL; newToc->relkind = opts->relkind; newToc->owner = opts->owner ? pg_strdup(opts->owner) : NULL; @@ -2395,6 +2397,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, AH->currUser = NULL; /* unknown */ AH->currSchema = NULL; /* ditto */ + AH->currSequenceAm = NULL; /* ditto */ AH->currTablespace = NULL; /* ditto */ AH->currTableAm = NULL; /* ditto */ @@ -2624,6 +2627,7 @@ WriteToc(ArchiveHandle *AH) WriteStr(AH, te->copyStmt); WriteStr(AH, te->namespace); WriteStr(AH, te->tablespace); + WriteStr(AH, te->sequenceam); WriteStr(AH, te->tableam); WriteInt(AH, te->relkind); WriteStr(AH, te->owner); @@ -2728,6 +2732,9 @@ ReadToc(ArchiveHandle *AH) if (AH->version >= K_VERS_1_10) te->tablespace = ReadStr(AH); + if (AH->version >= K_VERS_1_17) + te->sequenceam = ReadStr(AH); + if (AH->version >= K_VERS_1_14) te->tableam = ReadStr(AH); @@ -3399,6 +3406,9 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname) free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; + free(AH->currTableAm); AH->currTableAm = NULL; @@ -3561,6 +3571,57 @@ _selectTablespace(ArchiveHandle *AH, const char *tablespace) destroyPQExpBuffer(qry); } +/* + * Set the proper default_sequence_access_method value for the sequence. + */ +static void +_selectSequenceAccessMethod(ArchiveHandle *AH, const char *sequenceam) +{ + RestoreOptions *ropt = AH->public.ropt; + PQExpBuffer cmd; + const char *want, + *have; + + /* do nothing in --no-sequence-access-method mode */ + if (ropt->noSequenceAm) + return; + + have = AH->currSequenceAm; + want = sequenceam; + + if (!want) + return; + + if (have && strcmp(want, have) == 0) + return; + + cmd = createPQExpBuffer(); + appendPQExpBuffer(cmd, + "SET default_sequence_access_method = %s;", + fmtId(want)); + + if (RestoringToDB(AH)) + { + PGresult *res; + + res = PQexec(AH->connection, cmd->data); + + if (!res || PQresultStatus(res) != 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 = pg_strdup(want); +} + /* * Set the proper default_table_access_method value for the table. */ @@ -3770,6 +3831,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, const char *pfx) _becomeOwner(AH, te); _selectOutputSchema(AH, te->namespace); _selectTablespace(AH, te->tablespace); + _selectSequenceAccessMethod(AH, te->sequenceam); if (te->relkind != RELKIND_PARTITIONED_TABLE) _selectTableAccessMethod(AH, te->tableam); @@ -4288,6 +4350,8 @@ restore_toc_entries_prefork(ArchiveHandle *AH, TocEntry *pending_list) AH->currUser = NULL; free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; free(AH->currTablespace); AH->currTablespace = NULL; free(AH->currTableAm); @@ -5027,6 +5091,7 @@ CloneArchive(ArchiveHandle *AH) clone->connCancel = NULL; clone->currUser = NULL; clone->currSchema = NULL; + clone->currSequenceAm = NULL; clone->currTableAm = NULL; clone->currTablespace = NULL; @@ -5086,6 +5151,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_backup_archiver.h index a2064f471ede..5a091c03ce11 100644 --- a/src/bin/pg_dump/pg_backup_archiver.h +++ b/src/bin/pg_dump/pg_backup_archiver.h @@ -71,10 +71,11 @@ #define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0) /* BLOB METADATA entries * and multiple BLOBS, * relkind */ +#define K_VERS_1_17 MAKE_ARCHIVE_VERSION(1, 17, 0) /* add sequenceam */ /* 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_VERS_REV) @@ -323,6 +324,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 */ @@ -354,6 +356,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 relkind; /* relation kind, only for TABLE tags */ char *owner; @@ -395,6 +398,7 @@ typedef struct _archiveOpts const char *tag; const char *namespace; const char *tablespace; + const char *sequenceam; const char *tableam; char relkind; const char *owner; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index afd792871777..7c994fa04db4 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -133,6 +133,7 @@ typedef struct int64 cache; /* cache size */ int64 last_value; /* last value of sequence */ bool is_called; /* whether nextval advances before returning */ + char *seqam; /* access method of sequence */ } SequenceItem; typedef enum OidOptions @@ -486,6 +487,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}, @@ -1144,6 +1146,7 @@ main(int argc, char **argv) ropt->superuser = dopt.outputSuperuser; ropt->createDB = dopt.outputCreateDB; ropt->noOwner = dopt.outputNoOwner; + ropt->noSequenceAm = dopt.outputNoSequenceAm; ropt->noTableAm = dopt.outputNoTableAm; ropt->noTablespace = dopt.outputNoTablespaces; ropt->disable_triggers = dopt.disable_triggers; @@ -1263,6 +1266,7 @@ help(const char *progname) printf(_(" --no-security-labels do not dump security label assignments\n")); printf(_(" --no-statistics do not dump statistics\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); + printf(_(" --no-sequence-access-method do not sequence table access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); @@ -13668,6 +13672,9 @@ dumpAccessMethod(Archive *fout, const AccessMethodInfo *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; @@ -18057,26 +18064,40 @@ collectSequences(Archive *fout) * * Since version 18, we can gather the sequence data in this query with * pg_get_sequence_data(), but we only do so for non-schema-only dumps. + * + * Access methods for sequences are supported since version 18. */ if (fout->remoteVersion < 100000) return; - else if (fout->remoteVersion < 180000 || - (!fout->dopt->dumpData && !fout->dopt->sequence_data)) + else if (fout->remoteVersion < 180000) query = "SELECT seqrelid, format_type(seqtypid, NULL), " "seqstart, seqincrement, " "seqmax, seqmin, " "seqcache, seqcycle, " - "NULL, 'f' " + "NULL, 'f', NULL " "FROM pg_catalog.pg_sequence " "ORDER BY seqrelid"; + else if (!fout->dopt->dumpData && !fout->dopt->sequence_data) + query = "SELECT s.seqrelid, format_type(s.seqtypid, NULL), " + "s.seqstart, s.seqincrement, " + "s.seqmax, s.seqmin, " + "s.seqcache, s.seqcycle, " + "NULL, 'f', a.amname AS seqam " + "FROM pg_catalog.pg_sequence s " + "JOIN pg_class c ON (c.oid = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam) " + "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_get_sequence_data(seqrelid) " + query = "SELECT s.seqrelid, format_type(s.seqtypid, NULL), " + "s.seqstart, s.seqincrement, " + "s.seqmax, s.seqmin, " + "s.seqcache, s.seqcycle, " + "r.last_value, r.is_called, " + "a.amname AS seqam " + "FROM pg_catalog.pg_sequence s " + "JOIN pg_class c ON (c.oid = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam), " + "pg_get_sequence_data(s.seqrelid) r " "ORDER BY seqrelid;"; res = ExecuteSqlQuery(fout, query, PGRES_TUPLES_OK); @@ -18096,6 +18117,10 @@ collectSequences(Archive *fout) 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); + if (!PQgetisnull(res, i, 10)) + sequences[i].seqam = pg_strdup(PQgetvalue(res, i, 10)); + else + sequences[i].seqam = NULL; } PQclear(res); @@ -18167,6 +18192,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) 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); + seq->seqam = NULL; PQclear(res); } @@ -18289,6 +18315,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) ARCHIVE_OPTS(.tag = tbinfo->dobj.name, .namespace = tbinfo->dobj.namespace->dobj.name, .owner = tbinfo->rolname, + .sequenceam = seq->seqam, .description = "SEQUENCE", .section = SECTION_PRE_DATA, .createStmt = query->data, diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index e08672425263..2a33d158379a 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 = 0; static int disable_triggers = 0; static int if_exists = 0; static int inserts = 0; +static int no_sequence_access_method = 0; static int no_table_access_method = 0; static int no_tablespaces = 0; static int use_setsessauth = 0; @@ -165,6 +166,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}, @@ -443,6 +445,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) @@ -688,6 +692,7 @@ help(void) printf(_(" --no-statistics do not dump statistics\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); printf(_(" --no-sync do not wait for changes to be written safely to disk\n")); + printf(_(" --no-sequence-access-method do not dump sequence access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c index 13e4dc507e04..bbde36049a56 100644 --- a/src/bin/pg_dump/pg_restore.c +++ b/src/bin/pg_dump/pg_restore.c @@ -69,6 +69,7 @@ main(int argc, char **argv) static int enable_row_security = 0; static int if_exists = 0; static int no_data_for_failed_tables = 0; + static int outputNoSequenceAm = 0; static int outputNoTableAm = 0; static int outputNoTablespaces = 0; static int use_setsessauth = 0; @@ -120,6 +121,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}, @@ -381,6 +383,7 @@ main(int argc, char **argv) opts->disable_triggers = disable_triggers; opts->enable_row_security = enable_row_security; opts->noDataForFailedTables = no_data_for_failed_tables; + opts->noSequenceAm = outputNoSequenceAm; opts->noTableAm = outputNoTableAm; opts->noTablespace = outputNoTablespaces; opts->use_setsessauth = use_setsessauth; @@ -510,6 +513,7 @@ usage(const char *progname) printf(_(" --no-security-labels do not restore security labels\n")); printf(_(" --no-statistics do not restore statistics\n")); printf(_(" --no-subscriptions do not restore subscriptions\n")); + printf(_(" --no-sequence-access-method do not restore sequence access methods\n")); printf(_(" --no-table-access-method do not restore table access methods\n")); printf(_(" --no-tablespaces do not restore tablespace assignments\n")); printf(_(" --section=SECTION restore named section (pre-data, 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_dump.pl index 3945e4f0e2aa..3c922920a625 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -595,6 +595,13 @@ my %pgdump_runs = ( 'postgres', ], }, + no_sequence_access_method => { + dump_cmd => [ + 'pg_dump', '--no-sync', + "--file=$tempdir/no_sequence_access_method.sql", + '--no-sequence-access-method', 'postgres', + ], + }, no_table_access_method => { dump_cmd => [ 'pg_dump', '--no-sync', @@ -805,6 +812,7 @@ my %full_runs = ( no_owner => 1, no_privs => 1, no_statistics => 1, + no_sequence_access_method => 1, no_table_access_method => 1, pg_dumpall_dbprivs => 1, pg_dumpall_exclude => 1, @@ -4659,6 +4667,18 @@ my %tests = ( }, }, + 'CREATE ACCESS METHOD regress_test_sequence_am' => { + create_order => 11, + create_sql => + 'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;', + regexp => qr/^ + \QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;\E + \n/xm, + like => { + %full_runs, section_pre_data => 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 @@ -4687,6 +4707,35 @@ my %tests = ( }, }, + + # 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' => { + create_order => 12, + create_sql => ' + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_0 USING seqlocal; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_1 USING regress_sequence_am; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_2 USING seqlocal;', + regexp => qr/^ + \QSET default_sequence_access_method = 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 => { + %full_runs, %dump_test_schema_runs, section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + no_sequence_access_method => 1, + only_dump_measurement => 1, + }, + }, + 'CREATE MATERIALIZED VIEW regress_pg_dump_matview_am' => { create_order => 13, create_sql => ' diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index 1975054d7bfb..bb1de62aaba1 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -1164,6 +1164,23 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + + <para> + This option is ignored when emitting an archive (non-text) output + file. For the archive formats, you can specify the option when you + call <command>pg_restore</command>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml index c2fa5be95193..79acdac16bd7 100644 --- a/doc/src/sgml/ref/pg_dumpall.sgml +++ b/doc/src/sgml/ref/pg_dumpall.sgml @@ -516,6 +516,17 @@ exclude database <replaceable class="parameter">PATTERN</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml index 199ea3345f30..81957b39eebe 100644 --- a/doc/src/sgml/ref/pg_restore.sgml +++ b/doc/src/sgml/ref/pg_restore.sgml @@ -773,6 +773,17 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> -- 2.47.2 [text/x-diff] v12-0006-Sequence-access-methods-core-documentation.patch (9.5K, ../../[email protected]/7-v12-0006-Sequence-access-methods-core-documentation.patch) download | inline diff: From fca49a46d5973e6379ba52b1bc911308ff1da600 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:55:56 +0900 Subject: [PATCH v12 6/7] 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 a83545761085..dfdac5271f2f 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -9447,6 +9447,22 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-default-sequence-access-method" xreflabel="default_sequence_access_method"> + <term><varname>default_sequence_access_method</varname> (<type>string</type>) + <indexterm> + <primary><varname>default_sequence_access_method</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + This parameter specifies the default sequence access method to use when + creating sequences if the <command>CREATE SEQUENCE</command> + command does not explicitly specify an access method. The default is + <literal>local</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-default-tablespace" xreflabel="default_tablespace"> <term><varname>default_tablespace</varname> (<type>string</type>) <indexterm> diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 25fb99cee694..bc34378dd68b 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -95,6 +95,7 @@ <!ENTITY planstats SYSTEM "planstats.sgml"> <!ENTITY tableam SYSTEM "tableam.sgml"> <!ENTITY indexam SYSTEM "indexam.sgml"> +<!ENTITY sequenceam SYSTEM "sequenceam.sgml"> <!ENTITY nls SYSTEM "nls.sgml"> <!ENTITY plhandler SYSTEM "plhandler.sgml"> <!ENTITY fdwhandler SYSTEM "fdwhandler.sgml"> diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml index af476c82fcc1..9f369cc89930 100644 --- a/doc/src/sgml/postgres.sgml +++ b/doc/src/sgml/postgres.sgml @@ -258,6 +258,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 dae43dbaed58..3067dc4d4df0 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 <replaceable class="parameter">name</replaceable> <listitem> <para> This clause specifies the type of access method to define. - Only <literal>TABLE</literal> and <literal>INDEX</literal> - are supported at present. + Only <literal>TABLE</literal>, <literal>SEQUENCE</literal> and + <literal>INDEX</literal> are supported at present. </para> </listitem> </varlistentry> @@ -77,12 +77,15 @@ CREATE ACCESS METHOD <replaceable class="parameter">name</replaceable> declared to take a single argument of type <type>internal</type>, and its return type depends on the type of access method; for <literal>TABLE</literal> access methods, it must - be <type>table_am_handler</type> and for <literal>INDEX</literal> - access methods, it must be <type>index_am_handler</type>. + be <type>table_am_handler</type>; for <literal>INDEX</literal> + access methods, it must be <type>index_am_handler</type>; + for <literal>SEQUENCE</literal>, it must be + <type>sequence_am_handler</type>; 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 <xref linkend="tableam"/> and the index access method - API is described in <xref linkend="indexam"/>. + is described in <xref linkend="tableam"/>, the index access method + API is described in <xref linkend="indexam"/> and the sequence access + method is described in <xref linkend="sequenceam"/>. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/create_sequence.sgml index 1e283f13d15c..52c6096e4ba2 100644 --- a/doc/src/sgml/ref/create_sequence.sgml +++ b/doc/src/sgml/ref/create_sequence.sgml @@ -29,6 +29,7 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceab [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ] + [ USING <replaceable class="parameter">access_method</replaceable> ] </synopsis> </refsynopsisdiv> @@ -263,6 +264,17 @@ SELECT * FROM <replaceable>name</replaceable>; </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>USING</literal> <replaceable class="parameter">access_method</replaceable></term> + <listitem> + <para> + The <literal>USING</literal> option specifies which sequence access + method will be used when generating the sequence numbers. The default + is <literal>local</literal>. + </para> + </listitem> + </varlistentry> </variablelist> </refsect1> diff --git a/doc/src/sgml/sequenceam.sgml b/doc/src/sgml/sequenceam.sgml new file mode 100644 index 000000000000..a96170bfac03 --- /dev/null +++ b/doc/src/sgml/sequenceam.sgml @@ -0,0 +1,80 @@ +<!-- doc/src/sgml/sequenceam.sgml --> + +<chapter id="sequenceam"> + <title>Sequence Access Method Interface Definition</title> + + <indexterm> + <primary>Sequence Access Method</primary> + </indexterm> + <indexterm> + <primary>sequenceam</primary> + <secondary>Sequence Access Method</secondary> + </indexterm> + + <para> + This chapter explains the interface between the core + <productname>PostgreSQL</productname> system and <firstterm>sequence access + methods</firstterm>, 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. + </para> + + <para> + Each sequence access method is described by a row in the + <link linkend="catalog-pg-am"><structname>pg_am</structname></link> system + catalog. The <structname>pg_am</structname> entry specifies a name and a + <firstterm>handler function</firstterm> for the sequence access method. These + entries can be created and deleted using the + <xref linkend="sql-create-access-method"/> and + <xref linkend="sql-drop-access-method"/> SQL commands. + </para> + + <para> + A sequence access method handler function must be declared to accept a single + argument of type <type>internal</type> and to return the pseudo-type + <type>sequence_am_handler</type>. The argument is a dummy value that simply + 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 + <structname>SequenceAmRoutine</structname>, which contains everything that the + core code needs to know to make use of the sequence access method. The return + value needs to be of server lifetime, which is typically achieved by + defining it as a <literal>static const</literal> variable in global + scope. The <structname>SequenceAmRoutine</structname> struct, also called the + access method's <firstterm>API struct</firstterm>, 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 + <structname>SequenceAmRoutine</structname> structure (with comments inside + 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 <ulink url="https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/include/access/sequenceam.h;hb=HEAD"> + <filename>src/include/access/sequenceam.h</filename></ulink> file. + </para> + + <para> + Currently, the way a sequence access method stores data is fairly + unconstrained, and it is possible to use a predefined + <link linkend="tableam">Table Access Method</link> to store sequence + data. + </para> + + <para> + For crash safety, a sequence access method can use + <link linkend="wal"><acronym>WAL</acronym></link>, or a custom + implementation. + If <acronym>WAL</acronym> is chosen, either + <link linkend="generic-wal">Generic WAL Records</link> can be used, or a + <link linkend="custom-rmgr">Custom WAL Resource Manager</link> can be + implemented. + </para> + + <para> + Any developer of a new <literal>sequence access method</literal> can refer to + the existing <literal>local</literal> implementation present in + <filename>src/backend/access/sequence/local.c</filename> for details of + its implementation. + </para> +</chapter> -- 2.47.2 [text/x-diff] v12-0007-snowflake-Add-sequence-AM-based-on-it.patch (28.0K, ../../[email protected]/8-v12-0007-snowflake-Add-sequence-AM-based-on-it.patch) download | inline diff: From c0324525c4bfa217875d4f52e11fa5c802c5c620 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 26 Apr 2024 15:09:00 +0900 Subject: [PATCH v12 7/7] snowflake: Add sequence AM based on it This includes documentation and a basic implementation, though this is in a very early stage. --- doc/src/sgml/contrib.sgml | 1 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/snowflake.sgml | 100 ++++ contrib/Makefile | 1 + contrib/meson.build | 1 + contrib/snowflake/.gitignore | 3 + contrib/snowflake/Makefile | 19 + contrib/snowflake/expected/snowflake.out | 73 +++ contrib/snowflake/meson.build | 34 ++ contrib/snowflake/snowflake--1.0.sql | 21 + contrib/snowflake/snowflake.c | 570 +++++++++++++++++++++++ contrib/snowflake/snowflake.control | 5 + contrib/snowflake/sql/snowflake.sql | 29 ++ 13 files changed, 858 insertions(+) create mode 100644 doc/src/sgml/snowflake.sgml create mode 100644 contrib/snowflake/.gitignore create mode 100644 contrib/snowflake/Makefile create mode 100644 contrib/snowflake/expected/snowflake.out create mode 100644 contrib/snowflake/meson.build create mode 100644 contrib/snowflake/snowflake--1.0.sql create mode 100644 contrib/snowflake/snowflake.c create mode 100644 contrib/snowflake/snowflake.control create mode 100644 contrib/snowflake/sql/snowflake.sql diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml index 7c381949a533..7f55133bd2bd 100644 --- a/doc/src/sgml/contrib.sgml +++ b/doc/src/sgml/contrib.sgml @@ -167,6 +167,7 @@ CREATE EXTENSION <replaceable>extension_name</replaceable>; &seg; &sepgsql; &contrib-spi; + &snowflake; &sslinfo; &tablefunc; &tcn; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index bc34378dd68b..af5916de5a41 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -158,6 +158,7 @@ <!ENTITY seg SYSTEM "seg.sgml"> <!ENTITY contrib-spi SYSTEM "contrib-spi.sgml"> <!ENTITY sepgsql SYSTEM "sepgsql.sgml"> +<!ENTITY snowflake SYSTEM "snowflake.sgml"> <!ENTITY sslinfo SYSTEM "sslinfo.sgml"> <!ENTITY tablefunc SYSTEM "tablefunc.sgml"> <!ENTITY tcn SYSTEM "tcn.sgml"> diff --git a/doc/src/sgml/snowflake.sgml b/doc/src/sgml/snowflake.sgml new file mode 100644 index 000000000000..060699e7ecd1 --- /dev/null +++ b/doc/src/sgml/snowflake.sgml @@ -0,0 +1,100 @@ +<!-- doc/src/sgml/snowflake.sgml --> + +<sect1 id="snowflake" xreflabel="snowflake"> + <title>snowflake — sequence access method</title> + + <indexterm zone="snowflake"> + <primary>snowflake</primary> + </indexterm> + + <para> + <literal>snowflake</literal> provides a sequence access method based on + <ulink url="https://en.wikipedia.org/wiki/Snowflake_ID">Snowflake IDs</ulink>. + </para> + + <para> + A Snowflake ID (or snowflake) is a unique 64-bit identifier made of three + components: + <itemizedlist spacing="compact"> + <listitem><para>41 bits for a timestamp, epoch-adjusted in milli-seconds</para></listitem> + <listitem><para>10 bits for machine ID</para></listitem> + <listitem><para>12 bits for a sequence number</para></listitem> + </itemizedlist> + </para> + + <sect2 id="snowflake-functions"> + <title>Functions</title> + + <variablelist> + <varlistentry> + <term> + <function>snowflake_get(raw int8) returns record</function> + <indexterm> + <primary>snowflake_get</primary> + <secondary>function</secondary> + </indexterm> + </term> + + <listitem> + <para> + Returns a record made of the timestamp in milli-seconds, the machine ID + and the sequence number for a single snowflake ID. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-parameters"> + <title>Configuration Parameters</title> + + <variablelist> + <varlistentry> + <term> + <varname>snowflake.machine_id</varname> + <indexterm> + <primary><varname>snowflake.machine_id</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Machine ID assigned to the snowflake IDs used in the sequence. The + default value is <literal>1</literal>. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-examples"> + <title>Examples</title> + + <para> + This is an example of creating a snowflake sequence: + </para> + +<programlisting> +CREATE SEQUENCE snowflake_seq USING snowflake; +</programlisting> + + <para> + Similarly to the default sequence access method, snowflake sequences + can be queried as a table: + </para> + +<programlisting> + =# SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) +=# SELECT to_timestamp(time_ms / 1000), machine, counter + FROM snowflake_get(nextval('snowflake_seq')); + to_timestamp | machine | counter +------------------------+---------+--------- + 2024-04-26 14:28:26+09 | 1 | 3 +(1 row) +</programlisting> + </sect2> + +</sect1> diff --git a/contrib/Makefile b/contrib/Makefile index 952855d9b61b..26ec31734950 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -43,6 +43,7 @@ SUBDIRS = \ pg_walinspect \ postgres_fdw \ seg \ + snowflake \ spi \ tablefunc \ tcn \ diff --git a/contrib/meson.build b/contrib/meson.build index 1ba73ebd67a3..9f11d5b00e44 100644 --- a/contrib/meson.build +++ b/contrib/meson.build @@ -58,6 +58,7 @@ subdir('pg_walinspect') subdir('postgres_fdw') subdir('seg') subdir('sepgsql') +subdir('snowflake') subdir('spi') subdir('sslinfo') # start-scripts doesn't contain build products diff --git a/contrib/snowflake/.gitignore b/contrib/snowflake/.gitignore new file mode 100644 index 000000000000..44d119cfcc24 --- /dev/null +++ b/contrib/snowflake/.gitignore @@ -0,0 +1,3 @@ +# Generated subdirectories +/log/ +/results/ diff --git a/contrib/snowflake/Makefile b/contrib/snowflake/Makefile new file mode 100644 index 000000000000..fa5b48d565d8 --- /dev/null +++ b/contrib/snowflake/Makefile @@ -0,0 +1,19 @@ +# contrib/snowflake/Makefile + +MODULES = snowflake + +EXTENSION = snowflake +DATA = snowflake--1.0.sql + +REGRESS = snowflake + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/snowflake +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/snowflake/expected/snowflake.out b/contrib/snowflake/expected/snowflake.out new file mode 100644 index 000000000000..b7e469bf7384 --- /dev/null +++ b/contrib/snowflake/expected/snowflake.out @@ -0,0 +1,73 @@ +CREATE EXTENSION snowflake; +CREATE SEQUENCE snowflake_seq USING snowflake; +SET snowflake.machine_id = 2000; -- error +ERROR: 2000 is outside the valid range for parameter "snowflake.machine_id" (0 .. 1023) +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(lastval()); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 3 | t +(1 row) + +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + relpersistence +---------------- + u +(1 row) + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; + machine | counter +---------+--------- + 4 | 2 + 4 | 3 + 4 | 4 + 4 | 5 + 4 | 6 + 4 | 7 + 4 | 8 + 4 | 9 + 4 | 10 + 4 | 11 +(10 rows) + +DROP TABLE snowflake_tab; +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; diff --git a/contrib/snowflake/meson.build b/contrib/snowflake/meson.build new file mode 100644 index 000000000000..567669eea790 --- /dev/null +++ b/contrib/snowflake/meson.build @@ -0,0 +1,34 @@ +# Copyright (c) 2024, PostgreSQL Global Development Group + +snowflake_sources = files( + 'snowflake.c', +) + +if host_system == 'windows' + snowflake_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'snowflake', + '--FILEDESC', 'snowflake - sequence access method',]) +endif + +snowflake = shared_module('snowflake', + snowflake_sources, + kwargs: contrib_mod_args, +) +contrib_targets += bloom + +install_data( + 'snowflake.control', + 'snowflake--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'snowflake', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'snowflake', + ], + }, +} diff --git a/contrib/snowflake/snowflake--1.0.sql b/contrib/snowflake/snowflake--1.0.sql new file mode 100644 index 000000000000..bcb9d754f1b4 --- /dev/null +++ b/contrib/snowflake/snowflake--1.0.sql @@ -0,0 +1,21 @@ +/* contrib/snowflake/snowflake--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION snowflake" to load this file. \quit + +CREATE FUNCTION snowflake_sequenceam_handler(internal) + RETURNS sequence_am_handler + AS 'MODULE_PATHNAME' + LANGUAGE C; + +CREATE ACCESS METHOD snowflake + TYPE SEQUENCE HANDLER snowflake_sequenceam_handler; +COMMENT ON ACCESS METHOD snowflake IS 'snowflake sequence access method'; + +CREATE FUNCTION snowflake_get(IN raw int8, + OUT time_ms int8, + OUT machine int4, + OUT counter int4) + RETURNS record + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT diff --git a/contrib/snowflake/snowflake.c b/contrib/snowflake/snowflake.c new file mode 100644 index 000000000000..be4c4039ecd0 --- /dev/null +++ b/contrib/snowflake/snowflake.c @@ -0,0 +1,570 @@ +/*------------------------------------------------------------------------- + * + * snowflake.c + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * contrib/snowflake/snowflake.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <sys/time.h> + +#include "access/generic_xlog.h" +#include "access/sequenceam.h" +#include "access/xact.h" +#include "catalog/storage_xlog.h" +#include "commands/tablecmds.h" +#include "fmgr.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "storage/bufmgr.h" +#include "utils/guc.h" +#include "utils/numeric.h" + +PG_MODULE_MAGIC; + +/* "special area" of a snowflake's buffer page. */ +#define SNOWFLAKE_MAGIC 0x01 + +typedef struct snowflake_magic +{ + uint32 magic; +} snowflake_magic; + +/* ----------------------------------------------------------------------- + * Snowflake ID are 64-bit based, with the following structure: + * - 41 bits for an epoch-based timestamp, in milli-seconds. + * - 10 bits for a machine ID. + * - 12 bits for a sequence counter. + * + * The timestamp can be cut to an offset. The machine ID is controlled + * by a superuser GUC. Sequence properties apply to the sequence counter, + * as the other two are environment-dependent. + * ----------------------------------------------------------------------- + */ + +/* + * Helper routines to convert a snowflake ID from/to an int64. + */ +#define SNOWFLAKE_COUNTER_MASK 0x0000000000000FFF /* 12 bits */ +#define SNOWFLAKE_COUNTER_SHIFT 0 +#define SNOWFLAKE_MACHINE_ID_MASK 0x00000000000003FF /* 10 bits */ +#define SNOWFLAKE_MACHINE_ID_SHIFT 12 /* counter */ +#define SNOWFLAKE_TIMESTAMP_MASK 0x000001FFFFFFFFFF /* 41 bits */ +#define SNOWFLAKE_TIMESTAMP_SHIFT 22 /* machine ID + counter sizes */ + +typedef struct snowflake_id +{ + uint32 count; + uint32 machine; + uint64 time_ms; +} snowflake_id; + +#define snowflake_id_to_int64(id, raw) { \ + raw = (((id).count) & SNOWFLAKE_COUNTER_MASK) << SNOWFLAKE_COUNTER_SHIFT | \ + (((id).machine) & SNOWFLAKE_MACHINE_ID_MASK) << SNOWFLAKE_MACHINE_ID_SHIFT | \ + (((id).time_ms) & SNOWFLAKE_TIMESTAMP_MASK) << SNOWFLAKE_TIMESTAMP_SHIFT; \ +} + +#define int64_to_snowflake_id(raw, id) { \ + (id).count = ((raw) >> SNOWFLAKE_COUNTER_SHIFT) & SNOWFLAKE_COUNTER_MASK; \ + (id).machine = ((raw) >> SNOWFLAKE_MACHINE_ID_SHIFT) & SNOWFLAKE_MACHINE_ID_MASK; \ + (id).time_ms = ((raw) >> SNOWFLAKE_TIMESTAMP_SHIFT) & SNOWFLAKE_TIMESTAMP_MASK; \ +} + +/* + * Format of tuples stored in heap table associated to snowflake sequence. + */ +typedef struct FormData_snowflake_data +{ + /* enough to cover 12 bits of the internal counter */ + int16 count; + bool is_called; +} FormData_snowflake_data; + +typedef FormData_snowflake_data *Form_snowflake_data; + +/* + * Columns of a snowflake sequence relation. + */ +#define SNOWFLAKE_COL_COUNT 1 +#define SNOWFLAKE_COL_CALLED 2 + +#define SNOWFLAKE_COLS 2 + +/* GUC parameter */ +static int snowflake_machine_id = 1; + +PG_FUNCTION_INFO_V1(snowflake_sequenceam_handler); + +/* ----------------------------------------------------------------------- + * Interfaces for relation manipulation. + * ----------------------------------------------------------------------- + */ + +/* + * Initialize snowflake relation's fork with some data. + */ +static void +fill_snowflake_fork(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + snowflake_magic *sm; + OffsetNumber offnum; + GenericXLogState *state = NULL; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(snowflake_magic)); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + sm->magic = SNOWFLAKE_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= HEAP_XMAX_INVALID; + ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber); + + /* + * Initialize before entering in the critical section, as this does + * allocations. + */ + if (forkNum == INIT_FORKNUM) + state = GenericXLogStart(rel); + + START_CRIT_SECTION(); + + MarkBufferDirty(buf); + + offnum = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* + * Init forks have to be logged. These go through generic WAL records + * for simplicity's sake to save from the need of a custom RMGR. + */ + if (forkNum == INIT_FORKNUM) + { + (void) GenericXLogRegisterBuffer(state, buf, GENERIC_XLOG_FULL_IMAGE); + GenericXLogFinish(state); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * Initialize snowflake relation. + * + * This needs to handle both the initial and main forks. + */ +static void +fill_snowflake(Relation rel, HeapTuple tuple) +{ + SMgrRelation srel; + + Assert(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED); + + fill_snowflake_fork(rel, tuple, MAIN_FORKNUM); + + /* init fork */ + srel = smgropen(rel->rd_locator, INVALID_PROC_NUMBER); + smgrcreate(srel, INIT_FORKNUM, false); + log_smgrcreate(&rel->rd_locator, INIT_FORKNUM); + fill_snowflake_fork(rel, tuple, INIT_FORKNUM); + FlushRelationBuffers(rel); + smgrclose(srel); +} + +/* + * Read the current state of a snowflake sequence + * + * Given an opened sequence relation, lock the page buffer and find the tuple. + * + * *buf receives the reference to the pinned-and-ex-locked buffer. + * *seqdatatuple receives the reference to the sequence tuple proper. + * + * Returns value points to the data payload of the tuple. + */ +static Form_snowflake_data +read_snowflake(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + snowflake_magic *sm; + Form_snowflake_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SNOWFLAKE_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_snowflake_data) GETSTRUCT(seqdatatuple); + + return seq; +} + + +/* ------------------------------------------------------------------------ + * Callbacks for the snowflake 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 * +snowflake_sequenceam_get_table_am(void) +{ + return "heap"; +} + +/* + * snowflake_sequenceam_init + * + * Initialize relation of a snowflake sequence. This stores the sequence + * counter in an unlogged relation as timestamps ensure value unicity. + */ +static void +snowflake_sequenceam_init(Relation rel, int64 last_value, bool is_called) +{ + Datum values[SNOWFLAKE_COLS]; + bool nulls[SNOWFLAKE_COLS]; + int16 counter; + TupleDesc tupdesc; + HeapTuple tuple; + List *elts = NIL; + ListCell *lc; + ColumnDef *coldef = NULL; + AlterTableCmd *atcmd; + List *atcmds = NIL; + + /* Adjust last_value, depending on the defaults given */ + counter = ((int16) last_value) & SNOWFLAKE_COUNTER_MASK; + + /* + * Create unlogged relation with its attributes. + */ + coldef = makeColumnDef("count", INT2OID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + + foreach(lc, elts) + { + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + /* + * Switch the relation to be unlogged. This forces a rewrite, but + * the relation is empty so that's OK. + */ + RelationSetNewRelfilenumber(rel, RELPERSISTENCE_UNLOGGED); + + /* And insert its first tuple */ + values[0] = Int16GetDatum(counter); + nulls[0] = false; + values[1] = BoolGetDatum(is_called); + nulls[1] = false; + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, values, nulls); + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_nextval + * + * Return the next value for a snowflake sequence. + */ +static int64 +snowflake_sequenceam_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + Buffer buf; + Form_snowflake_data seq; + HeapTupleData seqdatatuple; + int64 result = 0; + snowflake_id id = {0}; + struct timeval tp; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* + * The logic here is quite simple, increment the counter until its + * threshold is reached and get back to the start. If the threshold + * is reached, wait 1ms to ensure a unique timestamp. There is no + * need to do a retry as the buffer is already locked. + */ + id.count = seq->count; + id.count++; + + if (id.count > (PG_INT16_MAX & SNOWFLAKE_COUNTER_MASK)) + { + /* + * Threshold reached, so wait a bit for force clock to a new + * timestamp. + */ + id.count = 1; + pg_usleep(1000L); /* 1ms */ + } + + /* Compute timestamp, with buffer locked */ + gettimeofday(&tp, NULL); + id.time_ms = (uint64) tp.tv_sec * 1000 + + tp.tv_usec / 1000; + + /* Machine ID */ + id.machine = snowflake_machine_id; + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->count = id.count; + seq->is_called = true; + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + /* Store the last value computed for lastval() */ + snowflake_id_to_int64(id, result); + *last = result; + return result; +} + +/* + * snowflake_sequenceam_setval + * + * Set the sequence value, manipulating only the sequence counter. + */ +static void +snowflake_sequenceam_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* Change the in-buffer tuple */ + START_CRIT_SECTION(); + seq->count = (next & SNOWFLAKE_COUNTER_MASK); + seq->is_called = iscalled; + MarkBufferDirty(buf); + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_get_state + * + * Return the last sequence counter value. + */ +static void +snowflake_sequenceam_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + seq = read_snowflake(rel, &buf, &seqdatatuple); + *last_value = seq->count; + *is_called = seq->is_called; + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_reset + * + * Reset the sequence, coming down to resetting its counter. + */ +static void +snowflake_sequenceam_reset(Relation rel, int64 startv, bool is_called, + bool reset_state) +{ + HeapTupleData seqdatatuple; + HeapTuple tuple; + Form_snowflake_data seq; + Buffer buf; + + /* lock buffer page and read tuple */ + (void) read_snowflake(rel, &buf, &seqdatatuple); + + /* copy the existing tuple */ + tuple = 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 = (Form_snowflake_data) GETSTRUCT(tuple); + seq->count = (startv & SNOWFLAKE_COUNTER_MASK); + seq->is_called = is_called; + + /* create new storage */ + RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence); + + /* insert the modified tuple into the page */ + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_change_persistence + * + * There is nothing to do here, the underneath relation has to remain + * unlogged and is set as such when creating the sequence. + */ +static void +snowflake_sequenceam_change_persistence(Relation rel, char newrelpersistence) +{ + /* Nothing to do here */ +} + +/* ------------------------------------------------------------------------ + * Definition of the snowflake sequence access method. + * ------------------------------------------------------------------------ + */ + +static const SequenceAmRoutine snowflake_sequenceam_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = snowflake_sequenceam_get_table_am, + .init = snowflake_sequenceam_init, + .nextval = snowflake_sequenceam_nextval, + .setval = snowflake_sequenceam_setval, + .get_state = snowflake_sequenceam_get_state, + .reset = snowflake_sequenceam_reset, + .change_persistence = snowflake_sequenceam_change_persistence +}; + +Datum +snowflake_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&snowflake_sequenceam_methods); +} + +/* Utility functions */ + +/* + * snowflake_get + * + * Return a tuple worth of snowflake ID data, in a readable shape. + */ +PG_FUNCTION_INFO_V1(snowflake_get); +Datum +snowflake_get(PG_FUNCTION_ARGS) +{ +#define SNOWFLAKE_GET_COLS 3 + int64 raw = PG_GETARG_INT64(0); + Datum *values; + bool *nulls; + TupleDesc tupdesc; + snowflake_id id; + + /* determine result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + int64_to_snowflake_id(raw, id); + + nulls = palloc0(sizeof(bool) * tupdesc->natts); + values = palloc0(sizeof(Datum) * tupdesc->natts); + + values[0] = Int64GetDatum(id.time_ms); + values[1] = Int32GetDatum(id.machine); + values[2] = Int32GetDatum(id.count); + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Entry point when loading extension. + */ +void +_PG_init(void) +{ + DefineCustomIntVariable("snowflake.machine_id", + "Machine ID to use with snowflake sequence.", + "Default value is 1.", + &snowflake_machine_id, + 1, 0, 1023, /* 10 bits as max */ + PGC_SUSET, + 0, NULL, NULL, NULL); +} diff --git a/contrib/snowflake/snowflake.control b/contrib/snowflake/snowflake.control new file mode 100644 index 000000000000..7b8c6089c25f --- /dev/null +++ b/contrib/snowflake/snowflake.control @@ -0,0 +1,5 @@ +# snowflake extension +comment = 'snowflake - sequence access method' +default_version = '1.0' +module_pathname = '$libdir/snowflake' +relocatable = true diff --git a/contrib/snowflake/sql/snowflake.sql b/contrib/snowflake/sql/snowflake.sql new file mode 100644 index 000000000000..395d166ba4bc --- /dev/null +++ b/contrib/snowflake/sql/snowflake.sql @@ -0,0 +1,29 @@ +CREATE EXTENSION snowflake; + +CREATE SEQUENCE snowflake_seq USING snowflake; + +SET snowflake.machine_id = 2000; -- error +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(lastval()); +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; +DROP TABLE snowflake_tab; + +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; -- 2.47.2 [application/pgp-signature] signature.asc (833B, ../../[email protected]/9-signature.asc) download ^ permalink raw reply [nested|flat] 14+ messages in thread
* Re: Sequence Access Methods, round two @ 2025-04-30 00:08 Michael Paquier <[email protected]> parent: Michael Paquier <[email protected]> 0 siblings, 0 replies; 14+ messages in thread From: Michael Paquier @ 2025-04-30 00:08 UTC (permalink / raw) To: Kirill Reshke <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Peter Smith <[email protected]>; Postgres hackers <[email protected]> On Fri, Mar 28, 2025 at 12:40:17PM +0500, Kirill Reshke wrote: > Looks like we entered a rebase loop here. I'm really interested in > moving this forward; furthermore, I am inclined to include these > patches into our PostgreSQL fork with modifications that we use for > sharding and cloud. Yes. I do have some folks poking at me about this patch set on a regular basis, mostly people working on cluster-like applications where they want to control the source of the sequence computation, and because they need to maintain some custom forking chirurgy around the sequence area. So I'd like to get something done for v19 in this area, but well, few people need to be interested in the whole topic for the moment. There was an unconference session last year about that, but there was no consensus reached with the topic drifting around the copy of sequence data in logical replication setups. What I have here is different. It cannot come down to me to take a decision and do something; that's not the way things work around here. > At first, general thought: Access Method is a term we use for > relations. Table access method is something that stores data and > knowledge on how to read this data, and index access method is > something that hints what data we should retrieve using some predicate > (or quals). But sequence... is generating data primitive? So, maybe > not Sequence access method but Sequence generate method? OTOH this > patch provides a way for generating sequence values by _accessing_ > remote storage or procedures, so maybe we are fine with this wording. "Access method" is kind of fit for me here. We have all the infrastructure in place for this purpose, with the utility commands and such. Duplicating all that seems a waste, and sequences are relations. > patches: > > 0003: > Patch uses "local" wording for build-in sequence, which is not > precise. As I understand, snowflake sequence state is local too. Yes, I am not wedded to this name. >> + /* >> + * Retrieve table access method used by a sequence to store its metadata. >> + */ >> + const char *(*get_table_am) (void); > > Looks like we force the sequence to have some table storage. I can > imagine cases where local storage is not used (and thus, not logged), > so can we have this optional? The patch set gives the option for sequences to define the set of attributes they want, and it's up to the callbacks to decide if they want to interact with the storage. An implementation can also choose to not WAL-log anything if they want; the callbacks have been designed to allow this case (see the in-memory sequence computation from upthread). The point is that not supporting a NULL makes all the logic around the relcache much easier to think about, for little gain, actually. And we still need to support the default case where a heap relation is required for the current in-core "local" sequences. So I'm not really excited about such cases as a whole. > 0007: > So, we use generic xlog for logging very small actual changes. I > understand this is out of topic here, but can we add a second option > for generic log to store FormData_snowflake_data which is a few bytes? > Maybe we should start different thread for that Without an agreement about how the basic callbacks should be shaped, I don't think that this is worth the time at this stage. Spoiler: I'm pretty happy with the way things are done in the patch as with 64-bit fields for sequences mapping only to integers (no 128b or more, no custom types). One is already set for life with 8 bytes. I am rebasing the patch set, there were a few things with the removal of the %lld and their associated (long long) casts. -- Michael Attachments: [text/x-diff] v14-0001-Remove-FormData_pg_sequence_data-from-init_param.patch (9.2K, ../../[email protected]/2-v14-0001-Remove-FormData_pg_sequence_data-from-init_param.patch) download | inline diff: From d6e69fd5dd731a9b1af7f911c9c83635903a2390 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:00:45 +0900 Subject: [PATCH v14 1/7] 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/sequence.c index 451ae6f7f694..08744c3e9112 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 rel, 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); 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 = makeNode(CreateStmt); @@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) /* 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); /* @@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) { case SEQ_COL_LASTVAL: coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(seqdataform.last_value); + value[i - 1] = Int64GetDatumFast(last_value); break; case SEQ_COL_LOG: coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); @@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) ObjectAddress address; Relation rel; HeapTuple seqtuple; + bool reset_state = false; + bool is_called; + int64 last_value; HeapTuple newdatatuple; /* 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 = heap_copytuple(&datatuple); newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); + last_value = newdataform->last_value; + is_called = newdataform->is_called; UnlockReleaseBuffer(buf); /* 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); /* 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 = last_value; + newdataform->is_called = is_called; + if (reset_state) + newdataform->log_cnt = 0; fill_seq_with_data(seqrel, newdatatuple); } @@ -1236,17 +1249,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). Also + * 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 overridden. * * Note: we force a sequence rewrite whenever we change parameters that affect - * 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. Currently, * 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 @@ -1257,7 +1272,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) { @@ -1363,11 +1380,11 @@ init_params(ParseState *pstate, List *options, bool for_identity, } /* - * 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 = 0; + *reset_state = true; /* AS type */ if (as_type != NULL) @@ -1416,7 +1433,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("INCREMENT must not be zero"))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1428,7 +1445,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, { seqform->seqcycle = boolVal(is_cycled->arg); Assert(BoolIsValid(seqform->seqcycle)); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { @@ -1439,7 +1456,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (max_value != NULL && max_value->arg) { seqform->seqmax = defGetInt64(max_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || max_value != NULL || reset_max_value) { @@ -1455,7 +1472,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmax = -1; /* descending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate maximum value. No need to check INT8 as seqmax is an int64 */ @@ -1471,7 +1488,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (min_value != NULL && min_value->arg) { seqform->seqmin = defGetInt64(min_value); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit || min_value != NULL || reset_min_value) { @@ -1487,7 +1504,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, } else seqform->seqmin = 1; /* ascending seq */ - seqdataform->log_cnt = 0; + *reset_state = true; } /* Validate minimum value. No need to check INT8 as seqmin is an int64 */ @@ -1538,30 +1555,30 @@ init_params(ParseState *pstate, List *options, bool for_identity, if (restart_value != NULL) { if (restart_value->arg != NULL) - seqdataform->last_value = defGetInt64(restart_value); + *last_value = defGetInt64(restart_value); else - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; - seqdataform->log_cnt = 0; + *last_value = seqform->seqstart; + *is_called = false; + *reset_state = true; } else if (isInit) { - seqdataform->last_value = seqform->seqstart; - seqdataform->is_called = false; + *last_value = seqform->seqstart; + *is_called = false; } /* 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 (%" PRId64 ") cannot be less than MINVALUE (%" PRId64 ")", - seqdataform->last_value, + *last_value, seqform->seqmin))); - if (seqdataform->last_value > seqform->seqmax) + if (*last_value > seqform->seqmax) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("RESTART value (%" PRId64 ") cannot be greater than MAXVALUE (%" PRId64 ")", - seqdataform->last_value, + *last_value, seqform->seqmax))); /* CACHE */ @@ -1573,7 +1590,7 @@ init_params(ParseState *pstate, List *options, bool for_identity, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("CACHE (%" PRId64 ") must be greater than zero", seqform->seqcache))); - seqdataform->log_cnt = 0; + *reset_state = true; } else if (isInit) { -- 2.49.0 [text/x-diff] v14-0002-Integrate-addition-of-attributes-for-sequences-w.patch (11.2K, ../../[email protected]/3-v14-0002-Integrate-addition-of-attributes-for-sequences-w.patch) download | inline diff: From 22f134c6339956b38b86ac5198949352f66b786c Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 19 Jan 2024 15:00:14 +0900 Subject: [PATCH v14 2/7] 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 4610fc61293b..5bdea762fc72 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2407,6 +2407,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/sequence.c index 08744c3e9112..20e8978c9346 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 = NIL; + List *atcmds = 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 = NIL; for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) { ColumnDef *coldef = NULL; @@ -198,7 +200,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) coldef->is_not_null = true; null[i - 1] = false; - stmt->tableElts = lappend(stmt->tableElts, coldef); + elts = lappend(elts, coldef); } stmt->relation = seq->sequence; @@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; + /* + * Initial relation has no attributes, these are added later. + */ + stmt->tableElts = NIL; address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + tupDesc = RelationGetDescr(rel); /* now initialize the sequence's data */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 2705cf11330d..dc4d459653a8 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4646,6 +4646,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 */ @@ -4941,6 +4942,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* Recursion occurs during execution phase */ pass = 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 = 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, @@ -5373,6 +5381,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 = ATExecAddColumn(wqueue, tab, rel, &cmd, cmd->recurse, false, @@ -6588,6 +6597,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 25fe3d580166..0629dc5387c0 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1667,7 +1667,11 @@ ProcessUtilitySlow(ParseState *pstate, break; case T_CreateSeqStmt: + EventTriggerAlterTableStart(parsetree); address = DefineSequence(pstate, (CreateSeqStmt *) parsetree); + /* stashed internally */ + commandCollected = true; + EventTriggerAlterTableEnd(); break; case T_AlterSeqStmt: diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/test/modules/test_ddl_deparse/expected/alter_table.out index 50d0354a3417..ed31059ef584 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 of sequence parent_b_seq NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type ADD COLUMN (and recurse) desc column b of table parent NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint parent_b_not_null on table parent @@ -70,7 +73,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 parent_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 of 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 table parent diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out index 5837ea484e40..310ce5a6baf5 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 of 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 14915f661a89..527c67995a94 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 of 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 of 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 of sequence datatype_table_is_small_seq NOTICE: DDL test: type simple, tag CREATE TABLE NOTICE: DDL test: type simple, tag CREATE INDEX NOTICE: DDL test: type simple, tag CREATE INDEX diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c index 193669f2bc1e..254fdf90c79a 100644 --- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c +++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c @@ -113,6 +113,9 @@ get_altertable_subcmdinfo(PG_FUNCTION_ARGS) case AT_AddColumn: strtype = "ADD COLUMN"; break; + case AT_AddColumnToSequence: + strtype = "ADD COLUMN TO SEQUENCE"; + break; case AT_AddColumnToView: strtype = "ADD COLUMN TO VIEW"; break; -- 2.49.0 [text/x-diff] v14-0003-Refactor-code-for-in-core-local-sequences.patch (54.5K, ../../[email protected]/4-v14-0003-Refactor-code-for-in-core-local-sequences.patch) download | inline diff: From a88a044c02f0f17d643f1c90f245ec5acfa31eb5 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Wed, 30 Apr 2025 08:26:43 +0900 Subject: [PATCH v14 3/7] Refactor code for in-core "local" sequences This commit restructures the code of in-core sequences into a new set of files: - seqdesc.c is renamed to seqlocaldesc.c. - seqlocalxlog.c to the code in the WAL replay logic. - seqlocalam.c to store a set of routines called from sequence.c, finishing the separation between the main sequence logic and the in-core sequences. - seqlocalam.h to store the AM-specific structures and routine related to the in-core sequences. WAL records are renamed to "SequenceLocal" with structures, variables and file structures mapping to that. --- src/include/access/rmgrlist.h | 2 +- src/include/access/seqlocalam.h | 58 ++ src/backend/access/rmgrdesc/Makefile | 2 +- src/backend/access/rmgrdesc/meson.build | 2 +- .../rmgrdesc/{seqdesc.c => seqlocaldesc.c} | 20 +- src/backend/access/sequence/Makefile | 2 +- src/backend/access/sequence/meson.build | 2 + src/backend/access/sequence/seqlocalam.c | 634 ++++++++++++++++++ src/backend/access/sequence/seqlocalxlog.c | 82 +++ src/backend/access/transam/rmgr.c | 1 + src/backend/commands/sequence.c | 634 +----------------- src/bin/pg_waldump/.gitignore | 2 +- src/bin/pg_waldump/rmgrdesc.c | 1 + src/bin/pg_waldump/t/001_basic.pl | 2 +- 14 files changed, 822 insertions(+), 622 deletions(-) create mode 100644 src/include/access/seqlocalam.h rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%) create mode 100644 src/backend/access/sequence/seqlocalam.c create mode 100644 src/backend/access/sequence/seqlocalxlog.c diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h index 8e7fc9db8778..b942b25350bc 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, btree_identify, btree_xlog PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL, hash_mask, NULL) PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, 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, NULL, seq_mask, NULL) +PG_RMGR(RM_SEQ_LOCAL_ID, "SequenceLocal", seq_local_redo, seq_local_desc, seq_local_identify, NULL, NULL, seq_local_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, NULL, brin_mask, NULL) PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL, NULL, NULL) diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h new file mode 100644 index 000000000000..225fb9a2cbeb --- /dev/null +++ b/src/include/access/seqlocalam.h @@ -0,0 +1,58 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.h + * Local sequence access method. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/access/seqlocalam.h + * + *------------------------------------------------------------------------- + */ +#ifndef SEQLOCALAM_H +#define SEQLOCALAM_H + +#include "access/xlogreader.h" +#include "storage/relfilelocator.h" +#include "utils/rel.h" + +/* XLOG stuff */ +#define XLOG_SEQ_LOCAL_LOG 0x00 + +typedef struct xl_seq_local_rec +{ + RelFileLocator locator; + /* SEQUENCE TUPLE DATA FOLLOWS AT THE END */ +} xl_seq_local_rec; + +/* + * The "special area" of a local sequence's buffer page looks like this. + */ +#define SEQ_LOCAL_MAGIC 0x1717 + +typedef struct seq_local_magic +{ + uint32 magic; +} seq_local_magic; + +extern void seq_local_redo(XLogReaderState *record); +extern void seq_local_desc(StringInfo buf, XLogReaderState *record); +extern const char *seq_local_identify(uint8 info); +extern void seq_local_mask(char *page, BlockNumber blkno); + +/* access routines */ +extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last); +extern const char *seq_local_get_table_am(void); +extern void seq_local_init(Relation rel, int64 last_value, bool is_called); +extern void seq_local_setval(Relation rel, int64 next, bool iscalled); +extern void seq_local_reset(Relation rel, int64 startv, bool is_called, + bool reset_state); +extern void seq_local_get_state(Relation rel, int64 *last_value, + bool *is_called); +extern void seq_local_change_persistence(Relation rel, + char newrelpersistence); + +#endif /* SEQLOCALAM_H */ diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile index cd95eec37f14..e5900ed77af5 100644 --- a/src/backend/access/rmgrdesc/Makefile +++ b/src/backend/access/rmgrdesc/Makefile @@ -24,7 +24,7 @@ OBJS = \ relmapdesc.o \ replorigindesc.o \ rmgrdesc_utils.o \ - seqdesc.o \ + seqlocaldesc.o \ smgrdesc.o \ spgdesc.o \ standbydesc.o \ diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build index 96c98e800c22..6b3b3e1e44c1 100644 --- a/src/backend/access/rmgrdesc/meson.build +++ b/src/backend/access/rmgrdesc/meson.build @@ -17,7 +17,7 @@ rmgr_desc_sources = files( 'relmapdesc.c', 'replorigindesc.c', 'rmgrdesc_utils.c', - 'seqdesc.c', + 'seqlocaldesc.c', 'smgrdesc.c', 'spgdesc.c', 'standbydesc.c', diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/seqlocaldesc.c similarity index 64% rename from src/backend/access/rmgrdesc/seqdesc.c rename to src/backend/access/rmgrdesc/seqlocaldesc.c index 0d289d77fcf7..6ffbcb2c4735 100644 --- a/src/backend/access/rmgrdesc/seqdesc.c +++ b/src/backend/access/rmgrdesc/seqlocaldesc.c @@ -1,44 +1,44 @@ /*------------------------------------------------------------------------- * - * seqdesc.c - * rmgr descriptor routines for commands/sequence.c + * seqlocaldesc.c + * rmgr descriptor routines for sequence/seqlocal.c * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * src/backend/access/rmgrdesc/seqdesc.c + * src/backend/access/rmgrdesc/seqlocaldesc.c * *------------------------------------------------------------------------- */ #include "postgres.h" -#include "commands/sequence.h" +#include "access/seqlocalam.h" void -seq_desc(StringInfo buf, XLogReaderState *record) +seq_local_desc(StringInfo buf, XLogReaderState *record) { char *rec = XLogRecGetData(record); uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - xl_seq_rec *xlrec = (xl_seq_rec *) rec; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) rec; - if (info == XLOG_SEQ_LOG) + if (info == XLOG_SEQ_LOCAL_LOG) appendStringInfo(buf, "rel %u/%u/%u", xlrec->locator.spcOid, xlrec->locator.dbOid, xlrec->locator.relNumber); } const char * -seq_identify(uint8 info) +seq_local_identify(uint8 info) { const char *id = NULL; switch (info & ~XLR_INFO_MASK) { - case XLOG_SEQ_LOG: - id = "LOG"; + case XLOG_SEQ_LOCAL_LOG: + id = "SEQ_LOCAL_LOG"; break; } diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile index 9f9d31f5425a..a15ceec1c0a0 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index ec9ab9b7e9db..8bc0e95e68c0 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -1,5 +1,7 @@ # Copyright (c) 2022-2025, PostgreSQL Global Development Group backend_sources += files( + 'seqlocalam.c', + 'seqlocalxlog.c', 'sequence.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c new file mode 100644 index 000000000000..e019a6f5a95d --- /dev/null +++ b/src/backend/access/sequence/seqlocalam.c @@ -0,0 +1,634 @@ +/*------------------------------------------------------------------------- + * + * seqlocalam.c + * Local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalam.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/multixact.h" +#include "access/seqlocalam.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 SEQ_LOCAL_LOG_VALS 32 + +/* Format of tuples stored in heap table associated to local sequences */ +typedef struct FormData_pg_seq_local_data +{ + int64 last_value; + int64 log_cnt; + bool is_called; +} FormData_pg_seq_local_data; + +typedef FormData_pg_seq_local_data *Form_pg_seq_local_data; + +/* + * Columns of a local sequence relation + */ +#define SEQ_LOCAL_COL_LASTVAL 1 +#define SEQ_LOCAL_COL_LOG 2 +#define SEQ_LOCAL_COL_CALLED 3 + +#define SEQ_LOCAL_COL_FIRSTCOL SEQ_LOCAL_COL_LASTVAL +#define SEQ_LOCAL_COL_LASTCOL SEQ_LOCAL_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_LOCAL_LOG_VALS 32 + +static Form_pg_seq_local_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 tuple + * + * *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_seq_local_data +read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + seq_local_magic *sm; + Form_pg_seq_local_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SEQ_LOCAL_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_pg_seq_local_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 == RELPERSISTENCE_UNLOGGED) + { + SMgrRelation srel; + + srel = 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 content + */ +static void +fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + seq_local_magic *sm; + OffsetNumber offnum; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(page); + sm->magic = SEQ_LOCAL_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + + XLogRegisterData(&xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData(tuple->t_data, tuple->t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_nextval() + * + * Allocate a new value for a local sequence, based on the sequence + * configuration. + */ +int64 +seq_local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + int64 result; + int64 fetch; + int64 next; + int64 rescnt = 0; + int64 log; + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + Page page; + bool logit = false; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + page = BufferGetPage(buf); + + *last = next = result = seq->last_value; + fetch = cache; + log = 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_LOCAL_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 = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = true; + } + else + { + XLogRecPtr redoptr = GetRedoRecPtr(); + + if (PageGetLSN(page) <= redoptr) + { + /* last update of seq was before checkpoint */ + fetch = log = fetch + SEQ_LOCAL_LOG_VALS; + logit = 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 >= 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\" (%" PRId64 ")", + RelationGetRelationName(rel), + maxv))); + next = minv; + } + else + next += incby; + } + else + { + /* descending sequence */ + if ((minv < 0 && next < minv - incby) || + (minv >= 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\" (%" PRId64 ")", + RelationGetRelationName(rel), + minv))); + next = maxv; + } + else + next += incby; + } + fetch--; + if (rescnt < cache) + { + log--; + rescnt++; + *last = next; + if (rescnt == 1) /* if it's first result - */ + result = next; /* it's what to return */ + } + } + + log -= fetch; /* adjust for any unfetched numbers */ + Assert(log >= 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_seq_local_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 = next; + seq->is_called = true; + seq->log_cnt = 0; + + xlrec.locator = rel->rd_locator; + + XLogRegisterData(&xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData(seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + /* Now update sequence tuple to the intended final state */ + seq->last_value = *last; /* last fetched number */ + seq->is_called = true; + seq->log_cnt = log; /* how much is logged */ + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + return result; +} + +/* + * seq_local_get_table_am() + * + * Return the table access method used by this sequence. + */ +const char * +seq_local_get_table_am(void) +{ + return "heap"; +} + +/* + * seq_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 + * the information guessed from pg_sequences. This is the first tuple + * inserted after the relation has been created, filling in its heap + * table. + */ +void +seq_local_init(Relation rel, int64 last_value, bool is_called) +{ + Datum value[SEQ_LOCAL_COL_LASTCOL]; + bool null[SEQ_LOCAL_COL_LASTCOL]; + List *elts = NIL; + List *atcmds = NIL; + ListCell *lc; + TupleDesc tupdesc; + HeapTuple tuple; + + /* + * Create relation (and fill value[] and null[] for the initial tuple). + */ + for (int i = SEQ_LOCAL_COL_FIRSTCOL; i <= SEQ_LOCAL_COL_LASTCOL; i++) + { + ColumnDef *coldef = NULL; + + switch (i) + { + case SEQ_LOCAL_COL_LASTVAL: + coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatumFast(last_value); + break; + case SEQ_LOCAL_COL_LOG: + coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); + value[i - 1] = Int64GetDatum(0); + break; + case SEQ_LOCAL_COL_CALLED: + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + value[i - 1] = BoolGetDatum(is_called); + break; + } + + coldef->is_not_null = true; + null[i - 1] = false; + elts = lappend(elts, coldef); + } + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, value, null); + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_setval() + * + * Callback for setval(). + */ +void +seq_local_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->last_value = next; /* last fetched number */ + seq->is_called = iscalled; + seq->log_cnt = 0; + + MarkBufferDirty(buf); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel)) + { + xl_seq_local_rec xlrec; + XLogRecPtr recptr; + Page page = BufferGetPage(buf); + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = rel->rd_locator; + XLogRegisterData(&xlrec, sizeof(xl_seq_local_rec)); + XLogRegisterData(seqdatatuple.t_data, seqdatatuple.t_len); + + recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_reset() + * + * Perform a hard reset on the local sequence, rewriting its heap data + * entirely. + */ +void +seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) +{ + Form_pg_seq_local_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 = 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 = (Form_pg_seq_local_data) GETSTRUCT(tuple); + seq->last_value = startv; + seq->is_called = is_called; + if (reset_state) + seq->log_cnt = 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 == InvalidTransactionId); + Assert(rel->rd_rel->relminmxid == InvalidMultiXactId); + + /* + * Insert the modified tuple into the new storage file. + */ + fill_seq_with_data(rel, tuple); +} + +/* + * seq_local_get_state() + * + * Retrieve the state of a local sequence. + */ +void +seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_seq_local_data seq; + + /* lock page buffer and read tuple */ + seq = read_seq_tuple(rel, &buf, &seqdatatuple); + + *last_value = seq->last_value; + *is_called = seq->is_called; + + UnlockReleaseBuffer(buf); +} + +/* + * seq_local_change_persistence() + * + * Persistence change for the local sequence Relation. + */ +void +seq_local_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/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c new file mode 100644 index 000000000000..db0ad969dbf2 --- /dev/null +++ b/src/backend/access/sequence/seqlocalxlog.c @@ -0,0 +1,82 @@ +/*------------------------------------------------------------------------- + * + * seqlocalxlog.c + * WAL replay logic for local sequence access manager + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocalxlog.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/bufmask.h" +#include "access/seqlocalam.h" +#include "access/xlogutils.h" +#include "storage/block.h" + +/* + * Mask a Sequence page before performing consistency checks on it. + */ +void +seq_local_mask(char *page, BlockNumber blkno) +{ + mask_page_lsn_and_checksum(page); + + mask_unused_space(page); +} + +void +seq_local_redo(XLogReaderState *record) +{ + XLogRecPtr lsn = record->EndRecPtr; + uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; + Buffer buffer; + Page page; + Page localpage; + char *item; + Size itemsz; + xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record); + seq_local_magic *sm; + + if (info != XLOG_SEQ_LOCAL_LOG) + elog(PANIC, "seq_redo: unknown op code %u", info); + + buffer = XLogInitBufferForRedo(record, 0); + page = (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 = (Page) palloc(BufferGetPageSize(buffer)); + + PageInit(localpage, BufferGetPageSize(buffer), sizeof(seq_local_magic)); + sm = (seq_local_magic *) PageGetSpecialPointer(localpage); + sm->magic = SEQ_LOCAL_MAGIC; + + item = (char *) xlrec + sizeof(xl_seq_local_rec); + itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec); + + if (PageAddItem(localpage, (Item) item, itemsz, + FirstOffsetNumber, false, false) == InvalidOffsetNumber) + elog(PANIC, "seq_local_redo: failed to add item to page"); + + PageSetLSN(localpage, lsn); + + memcpy(page, localpage, BufferGetPageSize(buffer)); + MarkBufferDirty(buffer); + UnlockReleaseBuffer(buffer); + + pfree(localpage); +} diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c index 1b7499726eb0..cc92268937b1 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/seqlocalam.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/sequence.c index 20e8978c9346..15ea0e24970c 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,6 +16,7 @@ #include "access/bufmask.h" #include "access/htup_details.h" +#include "access/seqlocalam.h" #include "access/multixact.h" #include "access/relation.h" #include "access/sequence.h" @@ -50,23 +51,6 @@ #include "utils/varlena.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 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 current * session. This is needed to hold onto nextval/currval state. (We can't @@ -96,13 +80,9 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */ */ static SeqTableData *last_used_seq = NULL; -static void fill_seq_with_data(Relation rel, HeapTuple tuple); -static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber 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_identity, 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 = NIL; - List *atcmds = NIL; - ListCell *lc; Datum pgs_values[Natts_pg_sequence]; bool pgs_nulls[Natts_pg_sequence]; - int i; /* * 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); - /* - * Create relation (and fill value[] and null[] for the tuple) - */ - for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) - { - ColumnDef *coldef = NULL; - - switch (i) - { - case SEQ_COL_LASTVAL: - coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatumFast(last_value); - break; - case SEQ_COL_LOG: - coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); - value[i - 1] = Int64GetDatum((int64) 0); - break; - case SEQ_COL_CALLED: - coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); - value[i - 1] = BoolGetDatum(false); - break; - } - - coldef->is_not_null = true; - null[i - 1] = false; - - elts = lappend(elts, coldef); - } - stmt->relation = seq->sequence; stmt->inhRelations = NIL; stmt->constraints = NIL; @@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) */ stmt->tableElts = NIL; + /* + * Initial relation has no attributes, these can be added later via the + * "init" AM callback. + */ + stmt->tableElts = NIL; + address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = sequence_open(seqoid, AccessExclusiveLock); - /* Add all the attributes to the sequence */ - foreach(lc, elts) - { - AlterTableCmd *atcmd; - - atcmd = makeNode(AlterTableCmd); - atcmd->subtype = AT_AddColumnToSequence; - atcmd->def = (Node *) lfirst(lc); - atcmds = lappend(atcmds, atcmd); - } - - /* - * No recursion needed. Note that EventTriggerAlterTableStart() should - * have been called. - */ - AlterTableInternal(RelationGetRelid(rel), atcmds, false); - CommandCounterIncrement(); - - tupDesc = RelationGetDescr(rel); - - /* now initialize the sequence's data */ - tuple = heap_form_tuple(tupDesc, value, null); - fill_seq_with_data(rel, tuple); + /* now initialize the sequence table structure and its data */ + seq_local_init(rel, last_value, is_called); /* 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); pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid)); if (!HeapTupleIsValid(pgstuple)) @@ -315,40 +240,8 @@ ResetSequence(Oid seq_relid) startv = pgsform->seqstart; ReleaseSysCache(pgstuple); - /* - * Copy the existing sequence tuple. - */ - tuple = 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 = (Form_pg_sequence_data) GETSTRUCT(tuple); - seq->last_value = startv; - seq->is_called = false; - seq->log_cnt = 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 == InvalidTransactionId); - Assert(seq_rel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - fill_seq_with_data(seq_rel, tuple); + /* Sequence state is forcibly reset here. */ + seq_local_reset(seq_rel, startv, false, true); /* 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); } -/* - * 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 == RELPERSISTENCE_UNLOGGED) - { - SMgrRelation srel; - - srel = 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 content - */ -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 = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, - EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); - Assert(BufferGetBlockNumber(buf) == 0); - - page = BufferGetPage(buf); - - PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(page); - sm->magic = SEQ_MAGIC; - - /* Now insert sequence tuple */ - - /* - * Since VACUUM does not process sequences, we have to force the tuple to - * have xmin = 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 |= 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 = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, - InvalidOffsetNumber, false, false); - if (offnum != FirstOffsetNumber) - elog(ERROR, "failed to add sequence tuple to page"); - - /* XLOG stuff */ - if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = rel->rd_locator; - - XLogRegisterData(&xlrec, sizeof(xl_seq_rec)); - XLogRegisterData(tuple->t_data, tuple->t_len); - - recptr = 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 = false; bool is_called; int64 last_value; - HeapTuple newdatatuple; /* Open and lock sequence, and check for ownership along the way. */ relid = RangeVarGetRelidExtended(stmt->sequence, @@ -507,16 +296,8 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) seqform = (Form_pg_sequence) GETSTRUCT(seqtuple); - /* 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 = heap_copytuple(&datatuple); - newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple); - last_value = newdataform->last_value; - is_called = newdataform->is_called; - - UnlockReleaseBuffer(buf); + /* Read sequence data */ + seq_local_get_state(seqrel, &last_value, &is_called); /* 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(); - /* - * 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 == InvalidTransactionId); - Assert(seqrel->rd_rel->relminmxid == InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - newdataform->last_value = last_value; - newdataform->is_called = is_called; - if (reset_state) - newdataform->log_cnt = 0; - fill_seq_with_data(seqrel, newdatatuple); + seq_local_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) { SeqTable elm; Relation seqrel; - Buffer buf; - HeapTupleData seqdatatuple; /* * ALTER SEQUENCE acquires this lock earlier. If we're processing an @@ -596,10 +353,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - (void) read_seq_tuple(seqrel, &buf, &seqdatatuple); - RelationSetNewRelfilenumber(seqrel, newrelpersistence); - fill_seq_with_data(seqrel, &seqdatatuple); - UnlockReleaseBuffer(buf); + seq_local_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -662,24 +416,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 = 0; + int64 result; bool cycle; - bool logit = false; /* open and lock sequence */ init_sequence(relid, &elm, &seqrel); @@ -724,105 +469,9 @@ nextval_internal(Oid relid, bool check_permissions) cycle = pgsform->seqcycle; ReleaseSysCache(pgstuple); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - page = BufferGetPage(buf); - - last = next = result = seq->last_value; - fetch = cache; - log = 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 = log = fetch + SEQ_LOG_VALS; - logit = true; - } - else - { - XLogRecPtr redoptr = GetRedoRecPtr(); - - if (PageGetLSN(page) <= redoptr) - { - /* last update of seq was before checkpoint */ - fetch = log = fetch + SEQ_LOG_VALS; - logit = 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 >= 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\" (%" PRId64 ")", - RelationGetRelationName(seqrel), - maxv))); - next = minv; - } - else - next += incby; - } - else - { - /* descending sequence */ - if ((minv < 0 && next < minv - incby) || - (minv >= 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\" (%" PRId64 ")", - RelationGetRelationName(seqrel), - minv))); - next = maxv; - } - else - next += incby; - } - fetch--; - if (rescnt < cache) - { - log--; - rescnt++; - last = next; - if (rescnt == 1) /* if it's first result - */ - result = next; /* it's what to return */ - } - } - - log -= fetch; /* adjust for any unfetched numbers */ - Assert(log >= 0); + /* retrieve next value from the access method */ + result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -832,69 +481,6 @@ nextval_internal(Oid relid, bool check_permissions) last_used_seq = elm; - /* - * 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 = next; - seq->is_called = true; - seq->log_cnt = 0; - - xlrec.locator = seqrel->rd_locator; - - XLogRegisterData(&xlrec, sizeof(xl_seq_rec)); - XLogRegisterData(seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - /* Now update sequence tuple to the intended final state */ - seq->last_value = last; /* last fetched number */ - seq->is_called = true; - seq->log_cnt = log; /* how much is logged */ - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); - sequence_close(seqrel, NoLock); return result; @@ -984,9 +570,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, @@ -1020,9 +603,6 @@ do_setval(Oid relid, int64 next, bool iscalled) */ PreventCommandIfParallelMode("setval()"); - /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); - if ((next < minv) || (next > maxv)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), @@ -1044,37 +624,8 @@ do_setval(Oid relid, int64 next, bool iscalled) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - /* ready to change the on-disk (or really, in-buffer) tuple */ - START_CRIT_SECTION(); - - seq->last_value = next; /* last fetched number */ - seq->is_called = iscalled; - seq->log_cnt = 0; - - MarkBufferDirty(buf); - - /* XLOG stuff */ - if (RelationNeedsWAL(seqrel)) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - Page page = BufferGetPage(buf); - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator = seqrel->rd_locator; - XLogRegisterData(&xlrec, sizeof(xl_seq_rec)); - XLogRegisterData(seqdatatuple.t_data, seqdatatuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); + /* Call the access method callback */ + seq_local_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1215,62 +766,6 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) } -/* - * Given an opened sequence relation, lock the page buffer and find the tuple - * - * *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 = ReadBuffer(rel, 0); - LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); - - page = BufferGetPage(*buf); - sm = (sequence_magic *) PageGetSpecialPointer(page); - - if (sm->magic != SEQ_MAGIC) - elog(ERROR, "bad magic number in sequence \"%s\": %08X", - RelationGetRelationName(rel), sm->magic); - - lp = PageGetItemId(page, FirstOffsetNumber); - Assert(ItemIdIsNormal(lp)); - - /* Note we currently only bother to set these two fields of *seqdatatuple */ - seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); - seqdatatuple->t_len = 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) != InvalidTransactionId) - { - HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); - seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; - seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; - MarkBufferDirtyHint(*buf, true); - } - - seq = (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 @@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; + bool is_called; + int64 last_value; - seq = read_seq_tuple(seqrel, &buf, &seqtuple); + seq_local_get_state(seqrel, &last_value, &is_called); - values[0] = Int64GetDatum(seq->last_value); - values[1] = BoolGetDatum(seq->is_called); - - UnlockReleaseBuffer(buf); + values[0] = Int64GetDatum(last_value); + values[1] = BoolGetDatum(is_called); } else memset(isnull, true, sizeof(isnull)); @@ -1908,17 +1400,9 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; - - seq = read_seq_tuple(seqrel, &buf, &seqtuple); - - is_called = seq->is_called; - result = seq->last_value; - - UnlockReleaseBuffer(buf); + seq_local_get_state(seqrel, &result, &is_called); } + sequence_close(seqrel, NoLock); if (is_called) @@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - -void -seq_redo(XLogReaderState *record) -{ - XLogRecPtr lsn = record->EndRecPtr; - uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - Buffer buffer; - Page page; - Page localpage; - char *item; - Size itemsz; - xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record); - sequence_magic *sm; - - if (info != XLOG_SEQ_LOG) - elog(PANIC, "seq_redo: unknown op code %u", info); - - buffer = XLogInitBufferForRedo(record, 0); - page = (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 = (Page) palloc(BufferGetPageSize(buffer)); - - PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic)); - sm = (sequence_magic *) PageGetSpecialPointer(localpage); - sm->magic = SEQ_MAGIC; - - item = (char *) xlrec + sizeof(xl_seq_rec); - itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec); - - if (PageAddItem(localpage, (Item) item, itemsz, - FirstOffsetNumber, false, false) == 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. */ @@ -1992,14 +1425,3 @@ ResetSequenceCaches(void) last_used_seq = 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 ec51f41c767e..8d1195de2637 100644 --- a/src/bin/pg_waldump/.gitignore +++ b/src/bin/pg_waldump/.gitignore @@ -10,13 +10,13 @@ /gistdesc.c /hashdesc.c /heapdesc.c +/seqlocaldesc.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 fac509ed134e..2fcf9fc4392a 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/seqlocalam.h" #include "access/multixact.h" #include "access/nbtxlog.h" #include "access/rmgr.h" diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl index f26d75e01cfd..2495a28d26a1 100644 --- a/src/bin/pg_waldump/t/001_basic.pl +++ b/src/bin/pg_waldump/t/001_basic.pl @@ -67,7 +67,7 @@ Btree Hash Gin Gist -Sequence +SequenceLocal SPGist BRIN CommitTs -- 2.49.0 [text/x-diff] v14-0004-Sequence-access-methods-backend-support.patch (64.1K, ../../[email protected]/5-v14-0004-Sequence-access-methods-backend-support.patch) download | inline diff: From ccdef9ec55c72cb003c3d66f537707dfa256f0e9 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Wed, 4 Dec 2024 08:09:46 +0900 Subject: [PATCH v14 4/7] Sequence access methods - backend support The "seqlocal" sequence AM is now plugged in as a handler in the relcache, and a set of callbacks in sequenceam.h. --- src/include/access/seqlocalam.h | 15 -- src/include/access/sequenceam.h | 181 ++++++++++++++++++ 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/meson.build | 1 + src/backend/access/sequence/seqlocalam.c | 41 +++- 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 | 23 +-- 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/psql/describe.c | 2 + src/bin/psql/tab-complete.in.c | 4 +- src/test/regress/expected/create_am.out | 55 ++++-- src/test/regress/expected/opr_sanity.out | 12 ++ src/test/regress/expected/psql.out | 96 +++++----- src/test/regress/expected/type_sanity.out | 12 +- src/test/regress/sql/create_am.sql | 24 ++- src/test/regress/sql/opr_sanity.sql | 10 + src/test/regress/sql/type_sanity.sql | 12 +- src/tools/pgindent/typedefs.list | 5 +- 40 files changed, 695 insertions(+), 182 deletions(-) create mode 100644 src/include/access/sequenceam.h create mode 100644 src/backend/access/sequence/sequenceamapi.c diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h index 225fb9a2cbeb..21936511ac2b 100644 --- a/src/include/access/seqlocalam.h +++ b/src/include/access/seqlocalam.h @@ -15,7 +15,6 @@ #include "access/xlogreader.h" #include "storage/relfilelocator.h" -#include "utils/rel.h" /* XLOG stuff */ #define XLOG_SEQ_LOCAL_LOG 0x00 @@ -41,18 +40,4 @@ extern void seq_local_desc(StringInfo buf, XLogReaderState *record); extern const char *seq_local_identify(uint8 info); extern void seq_local_mask(char *page, BlockNumber blkno); -/* access routines */ -extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last); -extern const char *seq_local_get_table_am(void); -extern void seq_local_init(Relation rel, int64 last_value, bool is_called); -extern void seq_local_setval(Relation rel, int64 next, bool iscalled); -extern void seq_local_reset(Relation rel, int64 startv, bool is_called, - bool reset_state); -extern void seq_local_get_state(Relation rel, int64 *last_value, - bool *is_called); -extern void seq_local_change_persistence(Relation rel, - char newrelpersistence); - #endif /* SEQLOCALAM_H */ diff --git a/src/include/access/sequenceam.h b/src/include/access/sequenceam.h new file mode 100644 index 000000000000..ac48c8b468be --- /dev/null +++ b/src/include/access/sequenceam.h @@ -0,0 +1,181 @@ +/*------------------------------------------------------------------------- + * + * sequenceam.h + * POSTGRES sequence access method definitions. + * + * + * Portions Copyright (c) 1996-2024, 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 "seqlocal" + +/* 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 value + * allocated. The result returned is the next value of the sequence computed. + */ +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); + +#endif /* SEQUENCEAM_H */ diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat index 26d15928a155..8f076fcec958 100644 --- a/src/include/catalog/pg_am.dat +++ b/src/include/catalog/pg_am.dat @@ -15,6 +15,9 @@ { oid => '2', oid_symbol => 'HEAP_TABLE_AM_OID', descr => 'heap table access method', amname => 'heap', amhandler => 'heap_tableam_handler', amtype => 't' }, +{ oid => '8051', oid_symbol => 'LOCAL_SEQUENCE_AM_OID', + descr => 'local sequence access method', + amname => 'seqlocal', amhandler => 'seq_local_sequenceam_handler', amtype => 's' }, { oid => '403', oid_symbol => 'BTREE_AM_OID', descr => 'b-tree index access method', amname => 'btree', amhandler => 'bthandler', amtype => 'i' }, diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h index 6e98a0930c27..080bea5031a9 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 */ #endif /* EXPOSE_TO_CLIENT_CODE */ diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 07d182da796a..012a2863c037 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -231,6 +231,12 @@ MAKE_SYSCACHE(RELNAMENSP, pg_class_relname_nsp_index, 128); (relkind) == RELKIND_TOASTVALUE || \ (relkind) == RELKIND_MATVIEW) +/* + * Relation kinds with a sequence access method (rd_sequenceam). + */ +#define RELKIND_HAS_SEQUENCE_AM(relkind) \ + ((relkind) == RELKIND_SEQUENCE) + #endif /* EXPOSE_TO_CLIENT_CODE */ extern int errdetail_relkind_not_supported(char relkind); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 62beb71da288..191489fcfcca 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -913,6 +913,12 @@ prorettype => 'table_am_handler', proargtypes => 'internal', prosrc => 'heap_tableam_handler' }, +# Sequence access method handlers +{ oid => '8209', descr => 'local sequence access method handler', + proname => 'seq_local_sequenceam_handler', provolatile => 'v', + prorettype => 'sequence_am_handler', proargtypes => 'internal', + prosrc => 'seq_local_sequenceam_handler' }, + # Index access method handlers { oid => '330', descr => 'btree index access method handler', proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler', @@ -7882,6 +7888,13 @@ { oid => '327', descr => 'I/O', proname => 'index_am_handler_out', prorettype => 'cstring', proargtypes => 'index_am_handler', prosrc => 'index_am_handler_out' }, +{ oid => '8207', descr => 'I/O', + proname => 'sequence_am_handler_in', proisstrict => 'f', + prorettype => 'sequence_am_handler', proargtypes => 'cstring', + prosrc => 'sequence_am_handler_in' }, +{ oid => '8208', descr => 'I/O', + proname => 'sequence_am_handler_out', prorettype => 'cstring', + proargtypes => 'sequence_am_handler', prosrc => 'sequence_am_handler_out' }, { oid => '3311', descr => 'I/O', proname => 'tsm_handler_in', proisstrict => 'f', prorettype => 'tsm_handler', proargtypes => 'cstring', prosrc => 'tsm_handler_in' }, diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat index 6dca77e0a22f..c42531b25533 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -627,6 +627,12 @@ typcategory => 'P', typinput => 'index_am_handler_in', typoutput => 'index_am_handler_out', typreceive => '-', typsend => '-', typalign => 'i' }, +{ oid => '8210', + descr => 'pseudo-type for the result of a sequence AM handler function', + typname => 'sequence_am_handler', typlen => '4', typbyval => 't', + typtype => 'p', typcategory => 'P', typinput => 'sequence_am_handler_in', + typoutput => 'sequence_am_handler_out', typreceive => '-', typsend => '-', + typalign => 'i' }, { oid => '3310', descr => 'pseudo-type for the result of a tablesample method function', typname => 'tsm_handler', typlen => '4', typbyval => 't', typtype => 'p', diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd9..6790728aced3 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); diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index 9ac0b67683d3..7693e9941fc9 100644 --- a/src/include/commands/sequence.h +++ b/src/include/commands/sequence.h @@ -22,35 +22,6 @@ #include "storage/relfilelocator.h" -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); -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 d1ca24dd32f0..b1c4155c9a91 100644 --- a/src/include/nodes/meson.build +++ b/src/include/nodes/meson.build @@ -10,6 +10,7 @@ node_support_input_i = [ 'access/amapi.h', 'access/cmptype.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 5bdea762fc72..79465fe4c54d 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3216,6 +3216,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; diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 799fa7ace684..82bb81561671 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -55,6 +55,8 @@ extern bool check_log_connections(char **newval, void **extra, GucSource source) extern void assign_log_connections(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 **extra, + 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 b552359915f1..4e418968253d 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; + /* + * 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/sequence/Makefile index a15ceec1c0a0..62006165a15f 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/access/sequence top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = seqlocalam.o seqlocalxlog.o sequence.o +OBJS = seqlocalam.o seqlocalxlog.o sequence.o sequenceamapi.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build index 8bc0e95e68c0..d82af34d538c 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -4,4 +4,5 @@ backend_sources += files( 'seqlocalam.c', 'seqlocalxlog.c', 'sequence.c', + 'sequenceamapi.c', ) diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c index e019a6f5a95d..5dec9d51ec82 100644 --- a/src/backend/access/sequence/seqlocalam.c +++ b/src/backend/access/sequence/seqlocalam.c @@ -17,6 +17,7 @@ #include "access/multixact.h" #include "access/seqlocalam.h" +#include "access/sequenceam.h" #include "access/xact.h" #include "access/xloginsert.h" #include "access/xlogutils.h" @@ -24,6 +25,7 @@ #include "commands/tablecmds.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "utils/builtins.h" /* @@ -230,10 +232,10 @@ fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) * Allocate a new value for a local sequence, based on the sequence * configuration. */ -int64 +static int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last) + int64 minv, int64 cache, bool cycle, + int64 *last) { int64 result; int64 fetch; @@ -417,7 +419,7 @@ seq_local_nextval(Relation rel, int64 incby, int64 maxv, * * Return the table access method used by this sequence. */ -const char * +static const char * seq_local_get_table_am(void) { return "heap"; @@ -432,7 +434,7 @@ seq_local_get_table_am(void) * inserted after the relation has been created, filling in its heap * table. */ -void +static void seq_local_init(Relation rel, int64 last_value, bool is_called) { Datum value[SEQ_LOCAL_COL_LASTCOL]; @@ -499,7 +501,7 @@ seq_local_init(Relation rel, int64 last_value, bool is_called) * * Callback for setval(). */ -void +static void seq_local_setval(Relation rel, int64 next, bool iscalled) { Buffer buf; @@ -547,7 +549,7 @@ seq_local_setval(Relation rel, int64 next, bool iscalled) * Perform a hard reset on the local sequence, rewriting its heap data * entirely. */ -void +static void seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) { Form_pg_seq_local_data seq; @@ -600,7 +602,7 @@ seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) * * Retrieve the state of a local sequence. */ -void +static void seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) { Buffer buf; @@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called) * * Persistence change for the local sequence Relation. */ -void +static void seq_local_change_persistence(Relation rel, char newrelpersistence) { Buffer buf; @@ -632,3 +634,24 @@ seq_local_change_persistence(Relation rel, char newrelpersistence) fill_seq_with_data(rel, &seqdatatuple); UnlockReleaseBuffer(buf); } + +/* ------------------------------------------------------------------------ + * Definition of the local sequence access method. + * ------------------------------------------------------------------------ + */ +static const SequenceAmRoutine seq_local_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = seq_local_get_table_am, + .init = seq_local_init, + .nextval = seq_local_nextval, + .setval = seq_local_setval, + .reset = seq_local_reset, + .get_state = seq_local_get_state, + .change_persistence = seq_local_change_persistence +}; + +Datum +seq_local_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&seq_local_methods); +} diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/sequence.c index 8b5303553702..f30317d1feae 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 sequences - * (in contrast to other relation types like indexes). + * (in contrast to other relation types like indexes) that are independent + * of individual sequence access methods. * *------------------------------------------------------------------------- */ diff --git a/src/backend/access/sequence/sequenceamapi.c b/src/backend/access/sequence/sequenceamapi.c new file mode 100644 index 000000000000..dd1a60d827a4 --- /dev/null +++ b/src/backend/access/sequence/sequenceamapi.c @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------- + * + * sequenceamapi.c + * general sequence access method routines + * + * Portions Copyright (c) 1996-2024, 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 = 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 = OidFunctionCall0(amhandler); + routine = (SequenceAmRoutine *) DatumGetPointer(datum); + + if (routine == NULL || !IsA(routine, SequenceAmRoutine)) + elog(ERROR, "sequence access method handler %u did not return a SequenceAmRoutine 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 != NULL); + Assert(routine->init != NULL); + Assert(routine->nextval != NULL); + Assert(routine->setval != NULL); + Assert(routine->reset != NULL); + Assert(routine->get_state != NULL); + Assert(routine->change_persistence != 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 = SearchSysCache1(AMOID, + ObjectIdGetDatum(amoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", amoid); + aform = (Form_pg_am) GETSTRUCT(tuple); + Assert(aform->amtype == AMTYPE_SEQUENCE); + amhandleroid = 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 == '\0') + { + GUC_check_errdetail("%s cannot be empty.", + "default_sequence_access_method"); + return false; + } + + if (strlen(*newval) >= 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 != InvalidOid) + { + if (!OidIsValid(get_sequence_am_oid(*newval, true))) + { + /* + * When source == 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 == 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 fbaed5359ad7..558e9a8f9695 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -1480,9 +1480,13 @@ 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. Partitioned tables may not have an * access method set. + * + * Sequences and tables are created with their access method ID + * given by the caller of this function. */ if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) || - (relkind == RELKIND_PARTITIONED_TABLE && OidIsValid(accessmtd))) + (relkind == RELKIND_PARTITIONED_TABLE && OidIsValid(accessmtd)) || + 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 58ed9d216cc0..e44633d13b60 100644 --- a/src/backend/commands/amcmds.c +++ b/src/backend/commands/amcmds.c @@ -15,6 +15,7 @@ #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); } +/* + * 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 = INDEX_AM_HANDLEROID; break; + case AMTYPE_SEQUENCE: + expectedType = SEQUENCE_AM_HANDLEROID; + break; case AMTYPE_TABLE: expectedType = TABLE_AM_HANDLEROID; break; diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 15ea0e24970c..fc2c18df4dc3 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,10 +16,10 @@ #include "access/bufmask.h" #include "access/htup_details.h" -#include "access/seqlocalam.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 = NIL; stmt->constraints = NIL; stmt->options = NIL; + stmt->accessMethod = seq->accessMethod ? pstrdup(seq->accessMethod) : NULL; stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; @@ -173,7 +174,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) rel = sequence_open(seqoid, AccessExclusiveLock); /* now initialize the sequence table structure and its data */ - seq_local_init(rel, last_value, is_called); + sequence_init(rel, last_value, is_called); /* process OWNED BY if given */ if (owned_by) @@ -241,7 +242,7 @@ ResetSequence(Oid seq_relid) ReleaseSysCache(pgstuple); /* Sequence state is forcibly reset here. */ - seq_local_reset(seq_rel, startv, false, true); + sequence_reset(seq_rel, startv, false, true); /* 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 = (Form_pg_sequence) GETSTRUCT(seqtuple); /* Read sequence data */ - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); /* 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(); - seq_local_reset(seqrel, last_value, is_called, reset_state); + sequence_reset(seqrel, last_value, is_called, reset_state); } /* Clear local cache so that we don't think we have cached numbers */ @@ -353,7 +354,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - seq_local_change_persistence(seqrel, newrelpersistence); + sequence_change_persistence(seqrel, newrelpersistence); sequence_close(seqrel, NoLock); } @@ -470,8 +471,8 @@ nextval_internal(Oid relid, bool check_permissions) ReleaseSysCache(pgstuple); /* retrieve next value from the access method */ - result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle, - &last); + result = sequence_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); /* save info in local cache */ elm->increment = incby; @@ -625,7 +626,7 @@ do_setval(Oid relid, int64 next, bool iscalled) GetTopTransactionId(); /* Call the access method callback */ - seq_local_setval(seqrel, next, iscalled); + sequence_setval(seqrel, next, iscalled); sequence_close(seqrel, NoLock); } @@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS) bool is_called; int64 last_value; - seq_local_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); values[0] = Int64GetDatum(last_value); values[1] = BoolGetDatum(is_called); @@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) !RELATION_IS_OTHER_TEMP(seqrel) && (RelationIsPermanent(seqrel) || !RecoveryInProgress())) { - seq_local_get_state(seqrel, &result, &is_called); + sequence_get_state(seqrel, &result, &is_called); } sequence_close(seqrel, NoLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index dc4d459653a8..5fa8928119f9 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" @@ -1025,14 +1026,18 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, } /* - * 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 != NULL) { - Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE); - accessMethodId = get_table_am_oid(stmt->accessMethod, false); + Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE || + RELKIND_HAS_SEQUENCE_AM(relkind)); + if (RELKIND_HAS_SEQUENCE_AM(relkind)) + accessMethodId = get_sequence_am_oid(stmt->accessMethod, false); + else + accessMethodId = get_table_am_oid(stmt->accessMethod, false); } else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE) { @@ -1045,6 +1050,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId)) accessMethodId = get_table_am_oid(default_table_access_method, false); } + else if (RELKIND_HAS_SEQUENCE_AM(relkind)) + { + accessMethodId = get_sequence_am_oid(default_sequence_access_method, false); + } /* * Create the relation. Inherited defaults and CHECK constraints are diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile index 77ddb9ca53f1..64d4dccc936f 100644 --- a/src/backend/nodes/Makefile +++ b/src/backend/nodes/Makefile @@ -48,6 +48,7 @@ node_headers = \ access/amapi.h \ access/cmptype.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 77659b0f7602..5e4ff23cf1e4 100644 --- a/src/backend/nodes/gen_node_support.pl +++ b/src/backend/nodes/gen_node_support.pl @@ -60,6 +60,7 @@ my @all_input_files = qw( access/amapi.h access/cmptype.h access/sdir.h + access/sequenceam.h access/tableam.h access/tsmapi.h commands/event_trigger.h @@ -84,6 +85,7 @@ my @nodetag_only_files = 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 3c4268b271a4..3a287c7f87a9 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -381,6 +381,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <str> copy_file_name access_method_clause attr_name table_access_method_clause name cursor_name file_name + sequence_access_method_clause cluster_index_specification %type <list> func_name handler_name qual_Op qual_all_Op subquery_Op @@ -4941,23 +4942,26 @@ RefreshMatViewStmt: CreateSeqStmt: CREATE OptTemp SEQUENCE qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); - $4->relpersistence = $2; n->sequence = $4; n->options = $5; + n->accessMethod = $6; n->ownerId = InvalidOid; n->if_not_exists = false; $$ = (Node *) n; } | CREATE OptTemp SEQUENCE IF_P NOT EXISTS qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n = makeNode(CreateSeqStmt); $7->relpersistence = $2; n->sequence = $7; n->options = $8; + n->accessMethod = $9; n->ownerId = InvalidOid; n->if_not_exists = true; $$ = (Node *) n; @@ -4994,6 +4998,11 @@ OptParenthesizedSeqOptList: '(' SeqOptList ')' { $$ = $2; } | /*EMPTY*/ { $$ = NIL; } ; +sequence_access_method_clause: + USING name { $$ = $2; } + | /*EMPTY*/ { $$ = NULL; } + ; + SeqOptList: SeqOptElem { $$ = list_make1($1); } | SeqOptList SeqOptElem { $$ = lappend($1, $2); } ; @@ -5997,6 +6006,7 @@ CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name am_type: INDEX { $$ = AMTYPE_INDEX; } + | SEQUENCE { $$ = AMTYPE_SEQUENCE; } | TABLE { $$ = AMTYPE_TABLE; } ; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 62015431fdf1..9ba0febe63c4 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" @@ -518,6 +519,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, seqstmt->sequence = makeRangeVar(snamespace, sname, -1); seqstmt->sequence->relpersistence = seqpersistence; seqstmt->options = seqoptions; + seqstmt->accessMethod = NULL; /* * 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/pseudotypes.c index 317a1f2b282f..68f160dda7d3 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/relcache.c index 68ff67de549a..2f6c0a12f3e3 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" @@ -302,6 +304,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); @@ -1225,8 +1228,7 @@ retry: if (relation->rd_rel->relkind == RELKIND_INDEX || relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) RelationInitIndexAccessInfo(relation); - else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || - relation->rd_rel->relkind == RELKIND_SEQUENCE) + else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)) RelationInitTableAccessMethod(relation); else if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { @@ -1235,6 +1237,8 @@ retry: * inherit. */ } + else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind)) + RelationInitSequenceAccessMethod(relation); else Assert(relation->rd_rel->relam == InvalidOid); @@ -1831,17 +1835,9 @@ RelationInitTableAccessMethod(Relation relation) HeapTuple tuple; Form_pg_am aform; - if (relation->rd_rel->relkind == 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 == InvalidOid); - relation->rd_amhandler = 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. @@ -1872,6 +1868,49 @@ RelationInitTableAccessMethod(Relation relation) InitTableAmRoutine(relation); } +/* + * 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 != InvalidOid); + relation->rd_amhandler = GetSequenceAmRoutineId(relation->rd_rel->relam); + + /* + * Now we can fetch the sequence AM's API struct. + */ + relation->rd_sequenceam = GetSequenceAmRoutine(relation->rd_amhandler); + + /* + * From the sequence AM, set its expected table access method. + */ + tableam_name = sequence_get_table_am(relation); + tableam_oid = get_table_am_oid(tableam_name, false); + + tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(tableam_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", + tableam_oid); + aform = (Form_pg_am) GETSTRUCT(tuple); + tableam_handler = aform->amhandler; + ReleaseSysCache(tuple); + + relation->rd_tableam = GetTableAmRoutine(tableam_handler); +} + /* * formrdesc * @@ -3711,14 +3750,17 @@ RelationBuildLocalRelation(const char *relname, rel->rd_rel->relam = accessmtd; /* - * 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); - if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(relkind)) RelationInitTableAccessMethod(rel); + else if (relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); /* * Leave index access method uninitialized, because the pg_index row has @@ -4343,13 +4385,21 @@ RelationCacheInitializePhase3(void) /* Reload tableam data if needed */ if (relation->rd_tableam == NULL && - (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE)) + (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))) { RelationInitTableAccessMethod(relation); Assert(relation->rd_tableam != NULL); restart = true; } + else if (relation->rd_sequenceam == NULL && + relation->rd_rel->relkind == RELKIND_SEQUENCE) + { + RelationInitSequenceAccessMethod(relation); + Assert(relation->rd_sequenceam != NULL); + + restart = true; + } /* Release hold on the relation */ RelationDecrementReferenceCount(relation); @@ -6422,8 +6472,10 @@ load_relcache_init_file(bool shared) nailed_rels++; /* Load table AM data */ - if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || rel->rd_rel->relkind == RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind)) RelationInitTableAccessMethod(rel); + else if (rel->rd_rel->relkind == RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); Assert(rel->rd_index == NULL); Assert(rel->rd_indextuple == NULL); @@ -6435,6 +6487,7 @@ load_relcache_init_file(bool shared) Assert(rel->rd_supportinfo == NULL); Assert(rel->rd_indoption == NULL); Assert(rel->rd_indcollation == NULL); + Assert(rel->rd_sequenceam == NULL); Assert(rel->rd_opcoptions == NULL); } diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 2f8cbd867599..daa550113a1f 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -33,6 +33,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" @@ -4341,6 +4342,17 @@ struct config_string ConfigureNamesString[] = check_default_table_access_method, NULL, NULL }, + { + {"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default sequence access method for new sequences."), + 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/utils/misc/postgresql.conf.sample index 34826d01380b..911528bd0da6 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -754,6 +754,7 @@ autovacuum_worker_slots = 16 # autovacuum worker slots to allocate # error #search_path = '"$user", public' # schema names #row_security = on +#default_sequence_access_method = 'seqlocal' #default_table_access_method = 'heap' #default_tablespace = '' # a tablespace name, '' uses the default #default_toast_compression = 'pglz' # 'pglz' or 'lz4' diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 1d08268393e3..4c7a9446b96c 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -168,10 +168,12 @@ describeAccessMethods(const char *pattern, bool verbose) "SELECT amname AS \"%s\",\n" " CASE amtype" " WHEN " CppAsString2(AMTYPE_INDEX) " THEN '%s'" + " WHEN " CppAsString2(AMTYPE_SEQUENCE) " THEN '%s'" " WHEN " CppAsString2(AMTYPE_TABLE) " THEN '%s'" " END AS \"%s\"", gettext_noop("Name"), gettext_noop("Index"), + gettext_noop("Sequence"), gettext_noop("Table"), gettext_noop("Type")); diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index c916b9299a80..5f8a5989adc5 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -2562,7 +2562,7 @@ match_previous_words(int pattern_id, 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 <name> AS */ else if (TailMatches("ALTER", "SEQUENCE", MatchAny, "AS")) COMPLETE_WITH_CS("smallint", "integer", "bigint"); @@ -3605,7 +3605,7 @@ match_previous_words(int pattern_id, 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/expected/create_am.out index c1a951572512..784870e603d1 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; @@ -514,9 +509,12 @@ CREATE TABLE tableam_parted_heapx (a text, b int) PARTITION 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 = 'seqlocal'; 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; @@ -533,18 +531,18 @@ FROM pg_class AS pc LEFT JOIN pg_am AS pa ON (pa.oid = pc.relam) WHERE pc.relname LIKE 'tableam_%_heapx' ORDER BY 3, 1, 2; - relkind | amname | relname ----------+--------+----------------------------- - f | | tableam_fdw_heapx - r | heap2 | tableam_parted_1_heapx - r | heap | tableam_parted_2_heapx - p | | tableam_parted_heapx - S | | tableam_seq_heapx - r | heap2 | tableam_tbl_heapx - r | heap2 | tableam_tblas_heapx - m | heap2 | tableam_tblmv_heapx - r | heap2 | tableam_tblselectinto_heapx - v | | tableam_view_heapx + relkind | amname | relname +---------+----------+----------------------------- + f | | tableam_fdw_heapx + r | heap2 | tableam_parted_1_heapx + r | heap | tableam_parted_2_heapx + p | | tableam_parted_heapx + S | seqlocal | tableam_seq_heapx + r | heap2 | tableam_tbl_heapx + r | heap2 | tableam_tblas_heapx + m | heap2 | tableam_tblmv_heapx + r | heap2 | tableam_tblselectinto_heapx + v | | tableam_view_heapx (10 rows) -- don't want to keep those tables, nor the default @@ -574,3 +572,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_dump support +-- Checks for sequence access methods +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_local_sequenceam_handler; +-- Create and use sequence +CREATE SEQUENCE test_seqam USING local2; +SELECT nextval('test_seqam'::regclass); + nextval +--------- + 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/expected/opr_sanity.out index 20bf9ea9cdf7..20b3d8520315 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1943,6 +1943,18 @@ WHERE p1.oid = a1.amhandler AND a1.amtype = 't' AND -----+--------+-----+--------- (0 rows) +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + oid | amname | oid | proname +-----+--------+-----+--------- +(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 cf48ae6d0c2e..2b7d6c69e1e1 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -5127,31 +5127,33 @@ Indexes: -- check printing info about access methods \dA List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA * List of access methods - Name | Type ---------+------- - brin | Index - btree | Index - gin | Index - gist | Index - hash | Index - heap | Table - heap2 | Table - spgist | Index -(8 rows) + Name | Type +----------+---------- + brin | Index + btree | Index + gin | Index + gist | Index + hash | Index + heap | Table + heap2 | Table + seqlocal | Sequence + spgist | Index +(9 rows) \dA h* List of access methods @@ -5176,32 +5178,34 @@ List of access methods \dA: extra argument "bar" ignored \dA+ - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ * - List of access methods - Name | Type | Handler | Description ---------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access 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 | - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description +----------+----------+------------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access 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 | + seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method + spgist | Index | spghandler | SP-GiST index access method +(9 rows) \dA+ h* List of access methods diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out index dd0c52ab08b5..c3218a36bc48 100644 --- a/src/test/regress/expected/type_sanity.out +++ b/src/test/regress/expected/type_sanity.out @@ -511,21 +511,21 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p', 'I') OR -----+--------- (0 rows) --- All tables, indexes, partitioned indexes and matviews should have an --- access method. +-- All tables, indexes, partitioned indexes, matviews and sequences should have +-- an access method. SELECT c1.oid, c1.relname FROM pg_class as c1 -WHERE c1.relkind NOT IN ('S', 'v', 'f', 'c', 'p') and +WHERE c1.relkind NOT IN ('v', 'f', 'c', 'p') and c1.relam = 0; oid | relname -----+--------- (0 rows) --- Conversely, sequences, views, foreign tables, types and partitioned --- tables shouldn't have them. +-- Conversely, views, foreign tables, types and partitioned tables +-- shouldn't have them. SELECT c1.oid, c1.relname FROM pg_class as c1 -WHERE c1.relkind IN ('S', 'v', 'f', 'c', 'p') and +WHERE c1.relkind IN ('v', 'f', 'c', 'p') and c1.relam != 0; oid | relname -----+--------- diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql index 754fe0c694bc..76a91cf8dd68 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 FROM tableam_tbl_heap2; -- CREATE VIEW doesn't support USING CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2; --- 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; @@ -327,9 +324,13 @@ CREATE TABLE tableam_parted_1_heapx PARTITION OF tableam_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; --- 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 = 'seqlocal'; 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; @@ -365,3 +366,16 @@ CREATE FOREIGN TABLE fp PARTITION OF tableam_parted_a_heap2 DEFAULT SERVER x; DROP ACCESS METHOD heap2; -- we intentionally leave the objects created above alive, to verify pg_dump support + +-- Checks for sequence access methods + +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_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 2fb3a8528781..e60eeab33c02 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -1236,6 +1236,16 @@ WHERE p1.oid = a1.amhandler AND a1.amtype = 't' AND OR p1.pronargs != 1 OR p1.proargtypes[0] != 'internal'::regtype); +-- 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 = a1.amhandler AND a1.amtype = 's' AND + (p1.prorettype != 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs != 1 + OR p1.proargtypes[0] != 'internal'::regtype); + -- **************** pg_amop **************** -- Look for illegal values in pg_amop fields diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql index c94dd83d3061..f1b6cd1091ef 100644 --- a/src/test/regress/sql/type_sanity.sql +++ b/src/test/regress/sql/type_sanity.sql @@ -370,18 +370,18 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p', 'I') OR relpersistence NOT IN ('p', 'u', 't') OR relreplident NOT IN ('d', 'n', 'f', 'i'); --- All tables, indexes, partitioned indexes and matviews should have an --- access method. +-- All tables, indexes, partitioned indexes, matviews and sequences should have +-- an access method. SELECT c1.oid, c1.relname FROM pg_class as c1 -WHERE c1.relkind NOT IN ('S', 'v', 'f', 'c', 'p') and +WHERE c1.relkind NOT IN ('v', 'f', 'c', 'p') and c1.relam = 0; --- Conversely, sequences, views, foreign tables, types and partitioned --- tables shouldn't have them. +-- Conversely, views, foreign tables, types and partitioned tables +-- shouldn't have them. SELECT c1.oid, c1.relname FROM pg_class as c1 -WHERE c1.relkind IN ('S', 'v', 'f', 'c', 'p') and +WHERE c1.relkind IN ('v', 'f', 'c', 'p') and c1.relam != 0; -- Indexes and partitioned indexes should have AMs of type 'i'. diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index e5879e00dffe..e0654ddecd48 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2679,6 +2679,7 @@ SeqScanState SeqTable SeqTableData SeqType +SequenceAmRoutine SequenceItem SerCommitSeqNo SerialControl @@ -3723,6 +3724,7 @@ list_sort_comparator loc_chunk local_relopt local_relopts +local_sequence_magic local_source local_ts_iter local_ts_radix_tree @@ -4011,7 +4013,6 @@ scram_state_enum script_error_callback_arg security_class_t sem_t -sepgsql_context_info_t sequence_magic set_join_pathlist_hook_type set_rel_pathlist_hook_type @@ -4236,6 +4237,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 @@ -4247,7 +4249,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 -- 2.49.0 [text/x-diff] v14-0005-Sequence-access-methods-dump-restore-support.patch (21.8K, ../../[email protected]/6-v14-0005-Sequence-access-methods-dump-restore-support.patch) download | inline diff: From 317d970805198e89bca7de42d344c807c4eae9f5 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Mon, 26 Aug 2024 13:25:19 +0900 Subject: [PATCH v14 5/7] 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 | 47 +++++++++++++++----- 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 | 49 +++++++++++++++++++++ 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, 207 insertions(+), 11 deletions(-) diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index af0007fb6d2f..62d86d69db45 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -97,6 +97,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 @@ -190,6 +191,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_backup_archiver.c index afa42337b110..3d63d8f6f6b5 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -181,6 +181,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) dopt->outputSuperuser = ropt->superuser; dopt->outputCreateDB = ropt->createDB; dopt->outputNoOwner = ropt->noOwner; + dopt->outputNoSequenceAm = ropt->noSequenceAm; dopt->outputNoTableAm = ropt->noTableAm; dopt->outputNoTablespaces = ropt->noTablespace; dopt->disable_triggers = ropt->disable_triggers; @@ -1246,6 +1247,7 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId, newToc->tag = pg_strdup(opts->tag); newToc->namespace = opts->namespace ? pg_strdup(opts->namespace) : NULL; newToc->tablespace = opts->tablespace ? pg_strdup(opts->tablespace) : NULL; + newToc->sequenceam = opts->sequenceam ? pg_strdup(opts->sequenceam) : NULL; newToc->tableam = opts->tableam ? pg_strdup(opts->tableam) : NULL; newToc->relkind = opts->relkind; newToc->owner = opts->owner ? pg_strdup(opts->owner) : NULL; @@ -2405,6 +2407,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, AH->currUser = NULL; /* unknown */ AH->currSchema = NULL; /* ditto */ + AH->currSequenceAm = NULL; /* ditto */ AH->currTablespace = NULL; /* ditto */ AH->currTableAm = NULL; /* ditto */ @@ -2672,6 +2675,7 @@ WriteToc(ArchiveHandle *AH) WriteStr(AH, te->copyStmt); WriteStr(AH, te->namespace); WriteStr(AH, te->tablespace); + WriteStr(AH, te->sequenceam); WriteStr(AH, te->tableam); WriteInt(AH, te->relkind); WriteStr(AH, te->owner); @@ -2776,6 +2780,9 @@ ReadToc(ArchiveHandle *AH) if (AH->version >= K_VERS_1_10) te->tablespace = ReadStr(AH); + if (AH->version >= K_VERS_1_17) + te->sequenceam = ReadStr(AH); + if (AH->version >= K_VERS_1_14) te->tableam = ReadStr(AH); @@ -3462,6 +3469,9 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname) free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; + free(AH->currTableAm); AH->currTableAm = NULL; @@ -3624,6 +3634,57 @@ _selectTablespace(ArchiveHandle *AH, const char *tablespace) destroyPQExpBuffer(qry); } +/* + * Set the proper default_sequence_access_method value for the sequence. + */ +static void +_selectSequenceAccessMethod(ArchiveHandle *AH, const char *sequenceam) +{ + RestoreOptions *ropt = AH->public.ropt; + PQExpBuffer cmd; + const char *want, + *have; + + /* do nothing in --no-sequence-access-method mode */ + if (ropt->noSequenceAm) + return; + + have = AH->currSequenceAm; + want = sequenceam; + + if (!want) + return; + + if (have && strcmp(want, have) == 0) + return; + + cmd = createPQExpBuffer(); + appendPQExpBuffer(cmd, + "SET default_sequence_access_method = %s;", + fmtId(want)); + + if (RestoringToDB(AH)) + { + PGresult *res; + + res = PQexec(AH->connection, cmd->data); + + if (!res || PQresultStatus(res) != 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 = pg_strdup(want); +} + /* * Set the proper default_table_access_method value for the table. */ @@ -3833,6 +3894,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, const char *pfx) _becomeOwner(AH, te); _selectOutputSchema(AH, te->namespace); _selectTablespace(AH, te->tablespace); + _selectSequenceAccessMethod(AH, te->sequenceam); if (te->relkind != RELKIND_PARTITIONED_TABLE) _selectTableAccessMethod(AH, te->tableam); @@ -4389,6 +4451,8 @@ restore_toc_entries_prefork(ArchiveHandle *AH, TocEntry *pending_list) AH->currUser = NULL; free(AH->currSchema); AH->currSchema = NULL; + free(AH->currSequenceAm); + AH->currSequenceAm = NULL; free(AH->currTablespace); AH->currTablespace = NULL; free(AH->currTableAm); @@ -5128,6 +5192,7 @@ CloneArchive(ArchiveHandle *AH) clone->connCancel = NULL; clone->currUser = NULL; clone->currSchema = NULL; + clone->currSequenceAm = NULL; clone->currTableAm = NULL; clone->currTablespace = NULL; @@ -5187,6 +5252,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_backup_archiver.h index 365073b3eae4..89b5cec22ac1 100644 --- a/src/bin/pg_dump/pg_backup_archiver.h +++ b/src/bin/pg_dump/pg_backup_archiver.h @@ -71,10 +71,11 @@ #define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0) /* BLOB METADATA entries * and multiple BLOBS, * relkind */ +#define K_VERS_1_17 MAKE_ARCHIVE_VERSION(1, 17, 0) /* add sequenceam */ /* 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_VERS_REV) @@ -323,6 +324,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 */ @@ -358,6 +360,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 relkind; /* relation kind, only for TABLE tags */ char *owner; @@ -404,6 +407,7 @@ typedef struct _archiveOpts const char *tag; const char *namespace; const char *tablespace; + const char *sequenceam; const char *tableam; char relkind; const char *owner; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index e2e7975b34e0..543a1d958436 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -134,6 +134,7 @@ typedef struct int64 cache; /* cache size */ int64 last_value; /* last value of sequence */ bool is_called; /* whether nextval advances before returning */ + char *seqam; /* access method of sequence */ } SequenceItem; typedef enum OidOptions @@ -495,6 +496,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}, @@ -1182,6 +1184,7 @@ main(int argc, char **argv) ropt->superuser = dopt.outputSuperuser; ropt->createDB = dopt.outputCreateDB; ropt->noOwner = dopt.outputNoOwner; + ropt->noSequenceAm = dopt.outputNoSequenceAm; ropt->noTableAm = dopt.outputNoTableAm; ropt->noTablespace = dopt.outputNoTablespaces; ropt->disable_triggers = dopt.disable_triggers; @@ -1303,6 +1306,7 @@ help(const char *progname) printf(_(" --no-security-labels do not dump security label assignments\n")); printf(_(" --no-statistics do not dump statistics\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); + printf(_(" --no-sequence-access-method do not sequence table access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); @@ -14084,6 +14088,9 @@ dumpAccessMethod(Archive *fout, const AccessMethodInfo *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; @@ -18492,26 +18499,40 @@ collectSequences(Archive *fout) * * Since version 18, we can gather the sequence data in this query with * pg_get_sequence_data(), but we only do so for non-schema-only dumps. + * + * Access methods for sequences are supported since version 18. */ if (fout->remoteVersion < 100000) return; - else if (fout->remoteVersion < 180000 || - (!fout->dopt->dumpData && !fout->dopt->sequence_data)) + else if (fout->remoteVersion < 180000) query = "SELECT seqrelid, format_type(seqtypid, NULL), " "seqstart, seqincrement, " "seqmax, seqmin, " "seqcache, seqcycle, " - "NULL, 'f' " + "NULL, 'f', NULL " "FROM pg_catalog.pg_sequence " "ORDER BY seqrelid"; + else if (!fout->dopt->dumpData && !fout->dopt->sequence_data) + query = "SELECT s.seqrelid, format_type(s.seqtypid, NULL), " + "s.seqstart, s.seqincrement, " + "s.seqmax, s.seqmin, " + "s.seqcache, s.seqcycle, " + "NULL, 'f', a.amname AS seqam " + "FROM pg_catalog.pg_sequence s " + "JOIN pg_class c ON (c.oid = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam) " + "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_get_sequence_data(seqrelid) " + query = "SELECT s.seqrelid, format_type(s.seqtypid, NULL), " + "s.seqstart, s.seqincrement, " + "s.seqmax, s.seqmin, " + "s.seqcache, s.seqcycle, " + "r.last_value, r.is_called, " + "a.amname AS seqam " + "FROM pg_catalog.pg_sequence s " + "JOIN pg_class c ON (c.oid = s.seqrelid) " + "JOIN pg_am a ON (a.oid = c.relam), " + "pg_get_sequence_data(s.seqrelid) r " "ORDER BY seqrelid;"; res = ExecuteSqlQuery(fout, query, PGRES_TUPLES_OK); @@ -18531,6 +18552,10 @@ collectSequences(Archive *fout) 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); + if (!PQgetisnull(res, i, 10)) + sequences[i].seqam = pg_strdup(PQgetvalue(res, i, 10)); + else + sequences[i].seqam = NULL; } PQclear(res); @@ -18602,6 +18627,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) 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); + seq->seqam = NULL; PQclear(res); } @@ -18724,6 +18750,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) ARCHIVE_OPTS(.tag = tbinfo->dobj.name, .namespace = tbinfo->dobj.namespace->dobj.name, .owner = tbinfo->rolname, + .sequenceam = seq->seqam, .description = "SEQUENCE", .section = SECTION_PRE_DATA, .createStmt = query->data, diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index 946a6d0fafc6..f8c6a74320ad 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -93,6 +93,7 @@ static int disable_dollar_quoting = 0; static int disable_triggers = 0; static int if_exists = 0; static int inserts = 0; +static int no_sequence_access_method = 0; static int no_table_access_method = 0; static int no_tablespaces = 0; static int use_setsessauth = 0; @@ -164,6 +165,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}, @@ -465,6 +467,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) @@ -738,6 +742,7 @@ help(void) printf(_(" --no-statistics do not dump statistics\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); printf(_(" --no-sync do not wait for changes to be written safely to disk\n")); + printf(_(" --no-sequence-access-method do not dump sequence access methods\n")); printf(_(" --no-table-access-method do not dump table access methods\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c index f2182e918256..73826fed9803 100644 --- a/src/bin/pg_dump/pg_restore.c +++ b/src/bin/pg_dump/pg_restore.c @@ -98,6 +98,7 @@ main(int argc, char **argv) static int enable_row_security = 0; static int if_exists = 0; static int no_data_for_failed_tables = 0; + static int outputNoSequenceAm = 0; static int outputNoTableAm = 0; static int outputNoTablespaces = 0; static int use_setsessauth = 0; @@ -154,6 +155,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}, @@ -459,6 +461,7 @@ main(int argc, char **argv) opts->disable_triggers = disable_triggers; opts->enable_row_security = enable_row_security; opts->noDataForFailedTables = no_data_for_failed_tables; + opts->noSequenceAm = outputNoSequenceAm; opts->noTableAm = outputNoTableAm; opts->noTablespace = outputNoTablespaces; opts->use_setsessauth = use_setsessauth; @@ -702,6 +705,7 @@ usage(const char *progname) printf(_(" --no-security-labels do not restore security labels\n")); printf(_(" --no-statistics do not restore statistics\n")); printf(_(" --no-subscriptions do not restore subscriptions\n")); + printf(_(" --no-sequence-access-method do not restore sequence access methods\n")); printf(_(" --no-table-access-method do not restore table access methods\n")); printf(_(" --no-tablespaces do not restore tablespace assignments\n")); printf(_(" --section=SECTION restore named section (pre-data, 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_dump.pl index 55d892d9c162..6c913d568b44 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -604,6 +604,13 @@ my %pgdump_runs = ( 'postgres', ], }, + no_sequence_access_method => { + dump_cmd => [ + 'pg_dump', '--no-sync', + "--file=$tempdir/no_sequence_access_method.sql", + '--no-sequence-access-method', 'postgres', + ], + }, no_table_access_method => { dump_cmd => [ 'pg_dump', '--no-sync', @@ -822,6 +829,7 @@ my %full_runs = ( no_policies => 1, no_privs => 1, no_statistics => 1, + no_sequence_access_method => 1, no_table_access_method => 1, pg_dumpall_dbprivs => 1, pg_dumpall_exclude => 1, @@ -4773,6 +4781,18 @@ my %tests = ( }, }, + 'CREATE ACCESS METHOD regress_test_sequence_am' => { + create_order => 11, + create_sql => + 'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;', + regexp => qr/^ + \QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;\E + \n/xm, + like => { + %full_runs, section_pre_data => 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 @@ -4801,6 +4821,35 @@ my %tests = ( }, }, + + # 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' => { + create_order => 12, + create_sql => ' + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_0 USING seqlocal; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_1 USING regress_sequence_am; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_2 USING seqlocal;', + regexp => qr/^ + \QSET default_sequence_access_method = 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 => { + %full_runs, %dump_test_schema_runs, section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + no_sequence_access_method => 1, + only_dump_measurement => 1, + }, + }, + 'CREATE MATERIALIZED VIEW regress_pg_dump_matview_am' => { create_order => 13, create_sql => ' diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index b757d27ebd0b..dda753e1cf25 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -1173,6 +1173,23 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + + <para> + This option is ignored when emitting an archive (non-text) output + file. For the archive formats, you can specify the option when you + call <command>pg_restore</command>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml index 43fdab2d77ed..17681045996a 100644 --- a/doc/src/sgml/ref/pg_dumpall.sgml +++ b/doc/src/sgml/ref/pg_dumpall.sgml @@ -605,6 +605,17 @@ exclude database <replaceable class="parameter">PATTERN</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml index b6de497aee18..78d5cd7e6011 100644 --- a/doc/src/sgml/ref/pg_restore.sgml +++ b/doc/src/sgml/ref/pg_restore.sgml @@ -829,6 +829,17 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-sequence-access-method</option></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-table-access-method</option></term> <listitem> -- 2.49.0 [text/x-diff] v14-0006-Sequence-access-methods-core-documentation.patch (9.5K, ../../[email protected]/7-v14-0006-Sequence-access-methods-core-documentation.patch) download | inline diff: From a93b0a521fdf82517ca8f16ecc880e9b01cfd3fb Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 1 Dec 2023 12:55:56 +0900 Subject: [PATCH v14 6/7] 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 fd6e3e028907..659a37960579 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -9752,6 +9752,22 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-default-sequence-access-method" xreflabel="default_sequence_access_method"> + <term><varname>default_sequence_access_method</varname> (<type>string</type>) + <indexterm> + <primary><varname>default_sequence_access_method</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + This parameter specifies the default sequence access method to use when + creating sequences if the <command>CREATE SEQUENCE</command> + command does not explicitly specify an access method. The default is + <literal>local</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-default-tablespace" xreflabel="default_tablespace"> <term><varname>default_tablespace</varname> (<type>string</type>) <indexterm> diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index fef9584f908e..a80de08dd26e 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -95,6 +95,7 @@ <!ENTITY planstats SYSTEM "planstats.sgml"> <!ENTITY tableam SYSTEM "tableam.sgml"> <!ENTITY indexam SYSTEM "indexam.sgml"> +<!ENTITY sequenceam SYSTEM "sequenceam.sgml"> <!ENTITY nls SYSTEM "nls.sgml"> <!ENTITY plhandler SYSTEM "plhandler.sgml"> <!ENTITY fdwhandler SYSTEM "fdwhandler.sgml"> diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml index af476c82fcc1..9f369cc89930 100644 --- a/doc/src/sgml/postgres.sgml +++ b/doc/src/sgml/postgres.sgml @@ -258,6 +258,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 dae43dbaed58..3067dc4d4df0 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 <replaceable class="parameter">name</replaceable> <listitem> <para> This clause specifies the type of access method to define. - Only <literal>TABLE</literal> and <literal>INDEX</literal> - are supported at present. + Only <literal>TABLE</literal>, <literal>SEQUENCE</literal> and + <literal>INDEX</literal> are supported at present. </para> </listitem> </varlistentry> @@ -77,12 +77,15 @@ CREATE ACCESS METHOD <replaceable class="parameter">name</replaceable> declared to take a single argument of type <type>internal</type>, and its return type depends on the type of access method; for <literal>TABLE</literal> access methods, it must - be <type>table_am_handler</type> and for <literal>INDEX</literal> - access methods, it must be <type>index_am_handler</type>. + be <type>table_am_handler</type>; for <literal>INDEX</literal> + access methods, it must be <type>index_am_handler</type>; + for <literal>SEQUENCE</literal>, it must be + <type>sequence_am_handler</type>; 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 <xref linkend="tableam"/> and the index access method - API is described in <xref linkend="indexam"/>. + is described in <xref linkend="tableam"/>, the index access method + API is described in <xref linkend="indexam"/> and the sequence access + method is described in <xref linkend="sequenceam"/>. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/create_sequence.sgml index 1e283f13d15c..52c6096e4ba2 100644 --- a/doc/src/sgml/ref/create_sequence.sgml +++ b/doc/src/sgml/ref/create_sequence.sgml @@ -29,6 +29,7 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceab [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ] + [ USING <replaceable class="parameter">access_method</replaceable> ] </synopsis> </refsynopsisdiv> @@ -263,6 +264,17 @@ SELECT * FROM <replaceable>name</replaceable>; </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>USING</literal> <replaceable class="parameter">access_method</replaceable></term> + <listitem> + <para> + The <literal>USING</literal> option specifies which sequence access + method will be used when generating the sequence numbers. The default + is <literal>local</literal>. + </para> + </listitem> + </varlistentry> </variablelist> </refsect1> diff --git a/doc/src/sgml/sequenceam.sgml b/doc/src/sgml/sequenceam.sgml new file mode 100644 index 000000000000..a96170bfac03 --- /dev/null +++ b/doc/src/sgml/sequenceam.sgml @@ -0,0 +1,80 @@ +<!-- doc/src/sgml/sequenceam.sgml --> + +<chapter id="sequenceam"> + <title>Sequence Access Method Interface Definition</title> + + <indexterm> + <primary>Sequence Access Method</primary> + </indexterm> + <indexterm> + <primary>sequenceam</primary> + <secondary>Sequence Access Method</secondary> + </indexterm> + + <para> + This chapter explains the interface between the core + <productname>PostgreSQL</productname> system and <firstterm>sequence access + methods</firstterm>, 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. + </para> + + <para> + Each sequence access method is described by a row in the + <link linkend="catalog-pg-am"><structname>pg_am</structname></link> system + catalog. The <structname>pg_am</structname> entry specifies a name and a + <firstterm>handler function</firstterm> for the sequence access method. These + entries can be created and deleted using the + <xref linkend="sql-create-access-method"/> and + <xref linkend="sql-drop-access-method"/> SQL commands. + </para> + + <para> + A sequence access method handler function must be declared to accept a single + argument of type <type>internal</type> and to return the pseudo-type + <type>sequence_am_handler</type>. The argument is a dummy value that simply + 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 + <structname>SequenceAmRoutine</structname>, which contains everything that the + core code needs to know to make use of the sequence access method. The return + value needs to be of server lifetime, which is typically achieved by + defining it as a <literal>static const</literal> variable in global + scope. The <structname>SequenceAmRoutine</structname> struct, also called the + access method's <firstterm>API struct</firstterm>, 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 + <structname>SequenceAmRoutine</structname> structure (with comments inside + 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 <ulink url="https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/include/access/sequenceam.h;hb=HEAD"> + <filename>src/include/access/sequenceam.h</filename></ulink> file. + </para> + + <para> + Currently, the way a sequence access method stores data is fairly + unconstrained, and it is possible to use a predefined + <link linkend="tableam">Table Access Method</link> to store sequence + data. + </para> + + <para> + For crash safety, a sequence access method can use + <link linkend="wal"><acronym>WAL</acronym></link>, or a custom + implementation. + If <acronym>WAL</acronym> is chosen, either + <link linkend="generic-wal">Generic WAL Records</link> can be used, or a + <link linkend="custom-rmgr">Custom WAL Resource Manager</link> can be + implemented. + </para> + + <para> + Any developer of a new <literal>sequence access method</literal> can refer to + the existing <literal>local</literal> implementation present in + <filename>src/backend/access/sequence/local.c</filename> for details of + its implementation. + </para> +</chapter> -- 2.49.0 [text/x-diff] v14-0007-snowflake-Add-sequence-AM-based-on-it.patch (28.0K, ../../[email protected]/8-v14-0007-snowflake-Add-sequence-AM-based-on-it.patch) download | inline diff: From 9fd87858065fc47bf3efd7ec867b2319e12000b6 Mon Sep 17 00:00:00 2001 From: Michael Paquier <[email protected]> Date: Fri, 26 Apr 2024 15:09:00 +0900 Subject: [PATCH v14 7/7] snowflake: Add sequence AM based on it This includes documentation and a basic implementation, though this is in a very early stage. --- doc/src/sgml/contrib.sgml | 1 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/snowflake.sgml | 100 ++++ contrib/Makefile | 1 + contrib/meson.build | 1 + contrib/snowflake/.gitignore | 3 + contrib/snowflake/Makefile | 19 + contrib/snowflake/expected/snowflake.out | 73 +++ contrib/snowflake/meson.build | 34 ++ contrib/snowflake/snowflake--1.0.sql | 21 + contrib/snowflake/snowflake.c | 570 +++++++++++++++++++++++ contrib/snowflake/snowflake.control | 5 + contrib/snowflake/sql/snowflake.sql | 29 ++ 13 files changed, 858 insertions(+) create mode 100644 doc/src/sgml/snowflake.sgml create mode 100644 contrib/snowflake/.gitignore create mode 100644 contrib/snowflake/Makefile create mode 100644 contrib/snowflake/expected/snowflake.out create mode 100644 contrib/snowflake/meson.build create mode 100644 contrib/snowflake/snowflake--1.0.sql create mode 100644 contrib/snowflake/snowflake.c create mode 100644 contrib/snowflake/snowflake.control create mode 100644 contrib/snowflake/sql/snowflake.sql diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml index 24b706b29adc..5e26d5baacc9 100644 --- a/doc/src/sgml/contrib.sgml +++ b/doc/src/sgml/contrib.sgml @@ -168,6 +168,7 @@ CREATE EXTENSION <replaceable>extension_name</replaceable>; &seg; &sepgsql; &contrib-spi; + &snowflake; &sslinfo; &tablefunc; &tcn; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index a80de08dd26e..bee964856ece 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -159,6 +159,7 @@ <!ENTITY seg SYSTEM "seg.sgml"> <!ENTITY contrib-spi SYSTEM "contrib-spi.sgml"> <!ENTITY sepgsql SYSTEM "sepgsql.sgml"> +<!ENTITY snowflake SYSTEM "snowflake.sgml"> <!ENTITY sslinfo SYSTEM "sslinfo.sgml"> <!ENTITY tablefunc SYSTEM "tablefunc.sgml"> <!ENTITY tcn SYSTEM "tcn.sgml"> diff --git a/doc/src/sgml/snowflake.sgml b/doc/src/sgml/snowflake.sgml new file mode 100644 index 000000000000..060699e7ecd1 --- /dev/null +++ b/doc/src/sgml/snowflake.sgml @@ -0,0 +1,100 @@ +<!-- doc/src/sgml/snowflake.sgml --> + +<sect1 id="snowflake" xreflabel="snowflake"> + <title>snowflake — sequence access method</title> + + <indexterm zone="snowflake"> + <primary>snowflake</primary> + </indexterm> + + <para> + <literal>snowflake</literal> provides a sequence access method based on + <ulink url="https://en.wikipedia.org/wiki/Snowflake_ID">Snowflake IDs</ulink>. + </para> + + <para> + A Snowflake ID (or snowflake) is a unique 64-bit identifier made of three + components: + <itemizedlist spacing="compact"> + <listitem><para>41 bits for a timestamp, epoch-adjusted in milli-seconds</para></listitem> + <listitem><para>10 bits for machine ID</para></listitem> + <listitem><para>12 bits for a sequence number</para></listitem> + </itemizedlist> + </para> + + <sect2 id="snowflake-functions"> + <title>Functions</title> + + <variablelist> + <varlistentry> + <term> + <function>snowflake_get(raw int8) returns record</function> + <indexterm> + <primary>snowflake_get</primary> + <secondary>function</secondary> + </indexterm> + </term> + + <listitem> + <para> + Returns a record made of the timestamp in milli-seconds, the machine ID + and the sequence number for a single snowflake ID. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-parameters"> + <title>Configuration Parameters</title> + + <variablelist> + <varlistentry> + <term> + <varname>snowflake.machine_id</varname> + <indexterm> + <primary><varname>snowflake.machine_id</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Machine ID assigned to the snowflake IDs used in the sequence. The + default value is <literal>1</literal>. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="snowflake-examples"> + <title>Examples</title> + + <para> + This is an example of creating a snowflake sequence: + </para> + +<programlisting> +CREATE SEQUENCE snowflake_seq USING snowflake; +</programlisting> + + <para> + Similarly to the default sequence access method, snowflake sequences + can be queried as a table: + </para> + +<programlisting> + =# SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) +=# SELECT to_timestamp(time_ms / 1000), machine, counter + FROM snowflake_get(nextval('snowflake_seq')); + to_timestamp | machine | counter +------------------------+---------+--------- + 2024-04-26 14:28:26+09 | 1 | 3 +(1 row) +</programlisting> + </sect2> + +</sect1> diff --git a/contrib/Makefile b/contrib/Makefile index 2f0a88d3f774..26cf6e94ff10 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -44,6 +44,7 @@ SUBDIRS = \ pg_walinspect \ postgres_fdw \ seg \ + snowflake \ spi \ tablefunc \ tcn \ diff --git a/contrib/meson.build b/contrib/meson.build index ed30ee7d639f..e3bd2b464172 100644 --- a/contrib/meson.build +++ b/contrib/meson.build @@ -59,6 +59,7 @@ subdir('pg_walinspect') subdir('postgres_fdw') subdir('seg') subdir('sepgsql') +subdir('snowflake') subdir('spi') subdir('sslinfo') # start-scripts doesn't contain build products diff --git a/contrib/snowflake/.gitignore b/contrib/snowflake/.gitignore new file mode 100644 index 000000000000..44d119cfcc24 --- /dev/null +++ b/contrib/snowflake/.gitignore @@ -0,0 +1,3 @@ +# Generated subdirectories +/log/ +/results/ diff --git a/contrib/snowflake/Makefile b/contrib/snowflake/Makefile new file mode 100644 index 000000000000..fa5b48d565d8 --- /dev/null +++ b/contrib/snowflake/Makefile @@ -0,0 +1,19 @@ +# contrib/snowflake/Makefile + +MODULES = snowflake + +EXTENSION = snowflake +DATA = snowflake--1.0.sql + +REGRESS = snowflake + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/snowflake +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/snowflake/expected/snowflake.out b/contrib/snowflake/expected/snowflake.out new file mode 100644 index 000000000000..b7e469bf7384 --- /dev/null +++ b/contrib/snowflake/expected/snowflake.out @@ -0,0 +1,73 @@ +CREATE EXTENSION snowflake; +CREATE SEQUENCE snowflake_seq USING snowflake; +SET snowflake.machine_id = 2000; -- error +ERROR: 2000 is outside the valid range for parameter "snowflake.machine_id" (0 .. 1023) +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(lastval()); + machine | counter +---------+--------- + 4 | 2 +(1 row) + +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + machine | counter +---------+--------- + 4 | 3 +(1 row) + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 3 | t +(1 row) + +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + relpersistence +---------------- + u +(1 row) + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + count | is_called +-------+----------- + 1 | f +(1 row) + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; + machine | counter +---------+--------- + 4 | 2 + 4 | 3 + 4 | 4 + 4 | 5 + 4 | 6 + 4 | 7 + 4 | 8 + 4 | 9 + 4 | 10 + 4 | 11 +(10 rows) + +DROP TABLE snowflake_tab; +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; diff --git a/contrib/snowflake/meson.build b/contrib/snowflake/meson.build new file mode 100644 index 000000000000..567669eea790 --- /dev/null +++ b/contrib/snowflake/meson.build @@ -0,0 +1,34 @@ +# Copyright (c) 2024, PostgreSQL Global Development Group + +snowflake_sources = files( + 'snowflake.c', +) + +if host_system == 'windows' + snowflake_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'snowflake', + '--FILEDESC', 'snowflake - sequence access method',]) +endif + +snowflake = shared_module('snowflake', + snowflake_sources, + kwargs: contrib_mod_args, +) +contrib_targets += bloom + +install_data( + 'snowflake.control', + 'snowflake--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'snowflake', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'snowflake', + ], + }, +} diff --git a/contrib/snowflake/snowflake--1.0.sql b/contrib/snowflake/snowflake--1.0.sql new file mode 100644 index 000000000000..bcb9d754f1b4 --- /dev/null +++ b/contrib/snowflake/snowflake--1.0.sql @@ -0,0 +1,21 @@ +/* contrib/snowflake/snowflake--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION snowflake" to load this file. \quit + +CREATE FUNCTION snowflake_sequenceam_handler(internal) + RETURNS sequence_am_handler + AS 'MODULE_PATHNAME' + LANGUAGE C; + +CREATE ACCESS METHOD snowflake + TYPE SEQUENCE HANDLER snowflake_sequenceam_handler; +COMMENT ON ACCESS METHOD snowflake IS 'snowflake sequence access method'; + +CREATE FUNCTION snowflake_get(IN raw int8, + OUT time_ms int8, + OUT machine int4, + OUT counter int4) + RETURNS record + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT diff --git a/contrib/snowflake/snowflake.c b/contrib/snowflake/snowflake.c new file mode 100644 index 000000000000..be4c4039ecd0 --- /dev/null +++ b/contrib/snowflake/snowflake.c @@ -0,0 +1,570 @@ +/*------------------------------------------------------------------------- + * + * snowflake.c + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * contrib/snowflake/snowflake.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <sys/time.h> + +#include "access/generic_xlog.h" +#include "access/sequenceam.h" +#include "access/xact.h" +#include "catalog/storage_xlog.h" +#include "commands/tablecmds.h" +#include "fmgr.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "storage/bufmgr.h" +#include "utils/guc.h" +#include "utils/numeric.h" + +PG_MODULE_MAGIC; + +/* "special area" of a snowflake's buffer page. */ +#define SNOWFLAKE_MAGIC 0x01 + +typedef struct snowflake_magic +{ + uint32 magic; +} snowflake_magic; + +/* ----------------------------------------------------------------------- + * Snowflake ID are 64-bit based, with the following structure: + * - 41 bits for an epoch-based timestamp, in milli-seconds. + * - 10 bits for a machine ID. + * - 12 bits for a sequence counter. + * + * The timestamp can be cut to an offset. The machine ID is controlled + * by a superuser GUC. Sequence properties apply to the sequence counter, + * as the other two are environment-dependent. + * ----------------------------------------------------------------------- + */ + +/* + * Helper routines to convert a snowflake ID from/to an int64. + */ +#define SNOWFLAKE_COUNTER_MASK 0x0000000000000FFF /* 12 bits */ +#define SNOWFLAKE_COUNTER_SHIFT 0 +#define SNOWFLAKE_MACHINE_ID_MASK 0x00000000000003FF /* 10 bits */ +#define SNOWFLAKE_MACHINE_ID_SHIFT 12 /* counter */ +#define SNOWFLAKE_TIMESTAMP_MASK 0x000001FFFFFFFFFF /* 41 bits */ +#define SNOWFLAKE_TIMESTAMP_SHIFT 22 /* machine ID + counter sizes */ + +typedef struct snowflake_id +{ + uint32 count; + uint32 machine; + uint64 time_ms; +} snowflake_id; + +#define snowflake_id_to_int64(id, raw) { \ + raw = (((id).count) & SNOWFLAKE_COUNTER_MASK) << SNOWFLAKE_COUNTER_SHIFT | \ + (((id).machine) & SNOWFLAKE_MACHINE_ID_MASK) << SNOWFLAKE_MACHINE_ID_SHIFT | \ + (((id).time_ms) & SNOWFLAKE_TIMESTAMP_MASK) << SNOWFLAKE_TIMESTAMP_SHIFT; \ +} + +#define int64_to_snowflake_id(raw, id) { \ + (id).count = ((raw) >> SNOWFLAKE_COUNTER_SHIFT) & SNOWFLAKE_COUNTER_MASK; \ + (id).machine = ((raw) >> SNOWFLAKE_MACHINE_ID_SHIFT) & SNOWFLAKE_MACHINE_ID_MASK; \ + (id).time_ms = ((raw) >> SNOWFLAKE_TIMESTAMP_SHIFT) & SNOWFLAKE_TIMESTAMP_MASK; \ +} + +/* + * Format of tuples stored in heap table associated to snowflake sequence. + */ +typedef struct FormData_snowflake_data +{ + /* enough to cover 12 bits of the internal counter */ + int16 count; + bool is_called; +} FormData_snowflake_data; + +typedef FormData_snowflake_data *Form_snowflake_data; + +/* + * Columns of a snowflake sequence relation. + */ +#define SNOWFLAKE_COL_COUNT 1 +#define SNOWFLAKE_COL_CALLED 2 + +#define SNOWFLAKE_COLS 2 + +/* GUC parameter */ +static int snowflake_machine_id = 1; + +PG_FUNCTION_INFO_V1(snowflake_sequenceam_handler); + +/* ----------------------------------------------------------------------- + * Interfaces for relation manipulation. + * ----------------------------------------------------------------------- + */ + +/* + * Initialize snowflake relation's fork with some data. + */ +static void +fill_snowflake_fork(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + snowflake_magic *sm; + OffsetNumber offnum; + GenericXLogState *state = NULL; + + /* Initialize first page of relation with special magic number */ + + buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) == 0); + + page = BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(snowflake_magic)); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + sm->magic = SNOWFLAKE_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin = 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 |= HEAP_XMAX_INVALID; + ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber); + + /* + * Initialize before entering in the critical section, as this does + * allocations. + */ + if (forkNum == INIT_FORKNUM) + state = GenericXLogStart(rel); + + START_CRIT_SECTION(); + + MarkBufferDirty(buf); + + offnum = PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum != FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* + * Init forks have to be logged. These go through generic WAL records + * for simplicity's sake to save from the need of a custom RMGR. + */ + if (forkNum == INIT_FORKNUM) + { + (void) GenericXLogRegisterBuffer(state, buf, GENERIC_XLOG_FULL_IMAGE); + GenericXLogFinish(state); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * Initialize snowflake relation. + * + * This needs to handle both the initial and main forks. + */ +static void +fill_snowflake(Relation rel, HeapTuple tuple) +{ + SMgrRelation srel; + + Assert(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED); + + fill_snowflake_fork(rel, tuple, MAIN_FORKNUM); + + /* init fork */ + srel = smgropen(rel->rd_locator, INVALID_PROC_NUMBER); + smgrcreate(srel, INIT_FORKNUM, false); + log_smgrcreate(&rel->rd_locator, INIT_FORKNUM); + fill_snowflake_fork(rel, tuple, INIT_FORKNUM); + FlushRelationBuffers(rel); + smgrclose(srel); +} + +/* + * Read the current state of a snowflake sequence + * + * Given an opened sequence relation, lock the page buffer and find the tuple. + * + * *buf receives the reference to the pinned-and-ex-locked buffer. + * *seqdatatuple receives the reference to the sequence tuple proper. + * + * Returns value points to the data payload of the tuple. + */ +static Form_snowflake_data +read_snowflake(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + snowflake_magic *sm; + Form_snowflake_data seq; + + *buf = ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page = BufferGetPage(*buf); + sm = (snowflake_magic *) PageGetSpecialPointer(page); + + if (sm->magic != SNOWFLAKE_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp = PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple */ + seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len = 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) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq = (Form_snowflake_data) GETSTRUCT(seqdatatuple); + + return seq; +} + + +/* ------------------------------------------------------------------------ + * Callbacks for the snowflake 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 * +snowflake_sequenceam_get_table_am(void) +{ + return "heap"; +} + +/* + * snowflake_sequenceam_init + * + * Initialize relation of a snowflake sequence. This stores the sequence + * counter in an unlogged relation as timestamps ensure value unicity. + */ +static void +snowflake_sequenceam_init(Relation rel, int64 last_value, bool is_called) +{ + Datum values[SNOWFLAKE_COLS]; + bool nulls[SNOWFLAKE_COLS]; + int16 counter; + TupleDesc tupdesc; + HeapTuple tuple; + List *elts = NIL; + ListCell *lc; + ColumnDef *coldef = NULL; + AlterTableCmd *atcmd; + List *atcmds = NIL; + + /* Adjust last_value, depending on the defaults given */ + counter = ((int16) last_value) & SNOWFLAKE_COUNTER_MASK; + + /* + * Create unlogged relation with its attributes. + */ + coldef = makeColumnDef("count", INT2OID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + coldef->is_not_null = true; + elts = lappend(elts, coldef); + + foreach(lc, elts) + { + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToSequence; + atcmd->def = (Node *) lfirst(lc); + atcmds = lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + /* + * Switch the relation to be unlogged. This forces a rewrite, but + * the relation is empty so that's OK. + */ + RelationSetNewRelfilenumber(rel, RELPERSISTENCE_UNLOGGED); + + /* And insert its first tuple */ + values[0] = Int16GetDatum(counter); + nulls[0] = false; + values[1] = BoolGetDatum(is_called); + nulls[1] = false; + + tupdesc = RelationGetDescr(rel); + tuple = heap_form_tuple(tupdesc, values, nulls); + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_nextval + * + * Return the next value for a snowflake sequence. + */ +static int64 +snowflake_sequenceam_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + Buffer buf; + Form_snowflake_data seq; + HeapTupleData seqdatatuple; + int64 result = 0; + snowflake_id id = {0}; + struct timeval tp; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* + * The logic here is quite simple, increment the counter until its + * threshold is reached and get back to the start. If the threshold + * is reached, wait 1ms to ensure a unique timestamp. There is no + * need to do a retry as the buffer is already locked. + */ + id.count = seq->count; + id.count++; + + if (id.count > (PG_INT16_MAX & SNOWFLAKE_COUNTER_MASK)) + { + /* + * Threshold reached, so wait a bit for force clock to a new + * timestamp. + */ + id.count = 1; + pg_usleep(1000L); /* 1ms */ + } + + /* Compute timestamp, with buffer locked */ + gettimeofday(&tp, NULL); + id.time_ms = (uint64) tp.tv_sec * 1000 + + tp.tv_usec / 1000; + + /* Machine ID */ + id.machine = snowflake_machine_id; + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->count = id.count; + seq->is_called = true; + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + /* Store the last value computed for lastval() */ + snowflake_id_to_int64(id, result); + *last = result; + return result; +} + +/* + * snowflake_sequenceam_setval + * + * Set the sequence value, manipulating only the sequence counter. + */ +static void +snowflake_sequenceam_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + /* lock page buffer and read tuple */ + seq = read_snowflake(rel, &buf, &seqdatatuple); + + /* Change the in-buffer tuple */ + START_CRIT_SECTION(); + seq->count = (next & SNOWFLAKE_COUNTER_MASK); + seq->is_called = iscalled; + MarkBufferDirty(buf); + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_get_state + * + * Return the last sequence counter value. + */ +static void +snowflake_sequenceam_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_snowflake_data seq; + + seq = read_snowflake(rel, &buf, &seqdatatuple); + *last_value = seq->count; + *is_called = seq->is_called; + UnlockReleaseBuffer(buf); +} + +/* + * snowflake_sequenceam_reset + * + * Reset the sequence, coming down to resetting its counter. + */ +static void +snowflake_sequenceam_reset(Relation rel, int64 startv, bool is_called, + bool reset_state) +{ + HeapTupleData seqdatatuple; + HeapTuple tuple; + Form_snowflake_data seq; + Buffer buf; + + /* lock buffer page and read tuple */ + (void) read_snowflake(rel, &buf, &seqdatatuple); + + /* copy the existing tuple */ + tuple = 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 = (Form_snowflake_data) GETSTRUCT(tuple); + seq->count = (startv & SNOWFLAKE_COUNTER_MASK); + seq->is_called = is_called; + + /* create new storage */ + RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence); + + /* insert the modified tuple into the page */ + fill_snowflake(rel, tuple); +} + +/* + * snowflake_sequenceam_change_persistence + * + * There is nothing to do here, the underneath relation has to remain + * unlogged and is set as such when creating the sequence. + */ +static void +snowflake_sequenceam_change_persistence(Relation rel, char newrelpersistence) +{ + /* Nothing to do here */ +} + +/* ------------------------------------------------------------------------ + * Definition of the snowflake sequence access method. + * ------------------------------------------------------------------------ + */ + +static const SequenceAmRoutine snowflake_sequenceam_methods = { + .type = T_SequenceAmRoutine, + .get_table_am = snowflake_sequenceam_get_table_am, + .init = snowflake_sequenceam_init, + .nextval = snowflake_sequenceam_nextval, + .setval = snowflake_sequenceam_setval, + .get_state = snowflake_sequenceam_get_state, + .reset = snowflake_sequenceam_reset, + .change_persistence = snowflake_sequenceam_change_persistence +}; + +Datum +snowflake_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&snowflake_sequenceam_methods); +} + +/* Utility functions */ + +/* + * snowflake_get + * + * Return a tuple worth of snowflake ID data, in a readable shape. + */ +PG_FUNCTION_INFO_V1(snowflake_get); +Datum +snowflake_get(PG_FUNCTION_ARGS) +{ +#define SNOWFLAKE_GET_COLS 3 + int64 raw = PG_GETARG_INT64(0); + Datum *values; + bool *nulls; + TupleDesc tupdesc; + snowflake_id id; + + /* determine result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + int64_to_snowflake_id(raw, id); + + nulls = palloc0(sizeof(bool) * tupdesc->natts); + values = palloc0(sizeof(Datum) * tupdesc->natts); + + values[0] = Int64GetDatum(id.time_ms); + values[1] = Int32GetDatum(id.machine); + values[2] = Int32GetDatum(id.count); + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Entry point when loading extension. + */ +void +_PG_init(void) +{ + DefineCustomIntVariable("snowflake.machine_id", + "Machine ID to use with snowflake sequence.", + "Default value is 1.", + &snowflake_machine_id, + 1, 0, 1023, /* 10 bits as max */ + PGC_SUSET, + 0, NULL, NULL, NULL); +} diff --git a/contrib/snowflake/snowflake.control b/contrib/snowflake/snowflake.control new file mode 100644 index 000000000000..7b8c6089c25f --- /dev/null +++ b/contrib/snowflake/snowflake.control @@ -0,0 +1,5 @@ +# snowflake extension +comment = 'snowflake - sequence access method' +default_version = '1.0' +module_pathname = '$libdir/snowflake' +relocatable = true diff --git a/contrib/snowflake/sql/snowflake.sql b/contrib/snowflake/sql/snowflake.sql new file mode 100644 index 000000000000..395d166ba4bc --- /dev/null +++ b/contrib/snowflake/sql/snowflake.sql @@ -0,0 +1,29 @@ +CREATE EXTENSION snowflake; + +CREATE SEQUENCE snowflake_seq USING snowflake; + +SET snowflake.machine_id = 2000; -- error +SET snowflake.machine_id = 4; -- ok +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(lastval()); +SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq')); +SELECT machine, counter FROM snowflake_get(currval('snowflake_seq')); + +-- Sequence relation exists, is unlogged and remains unlogged. +SELECT * FROM snowflake_seq; +ALTER SEQUENCE snowflake_seq SET LOGGED; +SELECT relpersistence FROM pg_class where relname = 'snowflake_seq'; + +ALTER SEQUENCE snowflake_seq RESTART; +SELECT * FROM snowflake_seq; + +-- Identity column, where cache affects value. +SET default_sequence_access_method = 'snowflake'; +CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int); +INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10)); +SELECT data.machine, data.counter + FROM snowflake_tab, LATERAL snowflake_get(a) AS data; +DROP TABLE snowflake_tab; + +DROP SEQUENCE snowflake_seq; +DROP EXTENSION snowflake; -- 2.49.0 [application/pgp-signature] signature.asc (833B, ../../[email protected]/9-signature.asc) download ^ permalink raw reply [nested|flat] 14+ messages in thread
end of thread, other threads:[~2025-04-30 00:08 UTC | newest] Thread overview: 14+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2022-12-10 03:40 [PATCH v5 3/3] Allow recovery via loadable modules. Nathan Bossart <[email protected]> 2024-03-14 00:40 Re: Sequence Access Methods, round two Michael Paquier <[email protected]> 2024-03-19 01:54 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]> 2024-04-19 07:00 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]> 2024-04-26 06:21 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]> 2024-06-20 06:12 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]> 2024-08-26 04:45 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]> 2024-08-30 08:24 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]> 2024-10-02 05:28 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]> 2024-12-03 09:24 ` Re: Sequence Access Methods, round two Kirill Reshke <[email protected]> 2024-12-03 23:36 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]> 2025-02-18 05:11 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]> 2025-02-25 07:25 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]> 2025-04-30 00:08 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox