-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION powa" to load this file. \quit

SET LOCAL statement_timeout = 0;
SET LOCAL client_encoding = 'UTF8';
SET LOCAL standard_conforming_strings = on;
SET LOCAL client_min_messages = warning;
SET LOCAL search_path = pg_catalog;

-- This table cannot be dumped as postgres doesn't offer a way to restore only
-- some columns.  Even if it was possible, we couldn't assume that users would
-- choose to restore the roles too.
CREATE TABLE @extschema@.powa_roles (
    powa_role name NOT NULL PRIMARY KEY,
    rolname name,
    CHECK (powa_role IN ('powa_admin', 'powa_read_all_data',
                         'powa_read_all_metrics', 'powa_write_all_data',
                         'powa_snapshot', 'powa_signal_backend'))
);

INSERT INTO @extschema@.powa_roles
    (powa_role,               rolname) VALUES
    ('powa_admin',            NULL),
    ('powa_read_all_data',    NULL),
    ('powa_read_all_metrics', NULL),
    ('powa_write_all_data',   NULL),
    ('powa_snapshot',         NULL),
    ('powa_signal_backend',   NULL);

CREATE FUNCTION @extschema@.setup_powa_roles(
    IN reuse_existing_role bool default FALSE,
    IN admin_role text DEFAULT 'powa_admin',
    IN read_all_data_role text DEFAULT 'powa_read_all_data',
    IN read_all_metrics_role text DEFAULT 'powa_read_all_metrics',
    IN write_all_data_role text DEFAULT 'powa_write_all_data',
    IN snapshot_role text DEFAULT 'powa_snapshot',
    IN signal_backend_role text DEFAULT 'powa_signal_backend'
) RETURNS void
AS $$
DECLARE
    v_roles name[];
    v_role name;
    v_rec record;
    v_nb integer;
BEGIN
    v_roles = ARRAY [admin_role, read_all_data_role, read_all_metrics_role,
                     write_all_data_role, snapshot_role, signal_backend_role];

    -- Preliminary sanity checks for the reuse_existing_role case
    IF (reuse_existing_role) THEN
        SELECT count(*) INTO v_nb
        FROM @extschema@.powa_roles
        WHERE rolname IS NOT NULL;

        IF (v_nb != 0) THEN
            RAISE EXCEPTION 'Cannot chang existing roles';
        END IF;

        FOR v_rec in SELECT *
                     FROM pg_catalog.pg_roles
                     WHERE rolname = ANY (v_roles)
        LOOP
            IF v_rec.rolsuper THEN
                RAISE EXCEPTION 'Existing role % is a superuser', v_rec.rolname;
            ELSIF v_rec.rolcreaterole THEN
                RAISE EXCEPTION 'Existing role % can create role', v_rec.rolname;
            ELSIF v_rec.rolcreatedb THEN
                RAISE EXCEPTION 'Existing role % can create db', v_rec.rolname;
            ELSIF v_rec.rolcanlogin THEN
                RAISE EXCEPTION 'Existing role % can login', v_rec.rolname;
            ELSIF v_rec.rolreplication THEN
                RAISE EXCEPTION 'Existing role % is a replication role', v_rec.rolname;
            ELSIF v_rec.rolbypassrls THEN
                RAISE EXCEPTION 'Existing role % can bypass RLS', v_rec.rolname;
            END IF;
        END LOOP;
    END IF;

    -- Update the powa_roles information.  This will be rollbacked if any issue
    -- is later found with it
    UPDATE @extschema@.powa_roles
        SET rolname = CASE
            WHEN powa_role = 'powa_admin' THEN admin_role
            WHEN powa_role = 'powa_read_all_data' THEN read_all_data_role
            WHEN powa_role = 'powa_read_all_metrics' THEN read_all_metrics_role
            WHEN powa_role = 'powa_write_all_data' THEN write_all_data_role
            WHEN powa_role = 'powa_snapshot' THEN snapshot_role
            WHEN powa_role = 'powa_signal_backend' THEN signal_backend_role
        END;

    IF (reuse_existing_role) THEN
        SELECT count(*) INTO v_nb
        FROM @extschema@.powa_roles p
        LEFT JOIN pg_catalog.pg_roles r USING (rolname)
        WHERE r.rolname IS NULL;

        IF (v_nb != 0) THEN
            RAISE EXCEPTION 'Cannot reuse existing powa roles unless all roles already exist';
        END IF;
    ELSE
        SELECT count(*) INTO v_nb
        FROM pg_catalog.pg_roles
        WHERE rolname = ANY (v_roles);

        IF v_nb != 0 THEN
            RAISE EXCEPTION 'Some roles already exists';
        END IF;

        FOREACH v_role IN ARRAY v_roles LOOP
            EXECUTE format('CREATE ROLE %I NOLOGIN', v_role);
        END LOOP;
    END IF;

    -- Final add all the required ACL based on the up-to-date powa_roles table
    PERFORM @extschema@.powa_grant();
END;
$$ LANGUAGE plpgsql STRICT
SET search_path = pg_catalog; /* end if setup_powa_roles */

CREATE TABLE @extschema@.powa_servers (
    id serial PRIMARY KEY,
    hostname text NOT NULL,
    alias text,
    port integer NOT NULL,
    username text NOT NULL,
    password text,
    dbname text NOT NULL,
    frequency integer NOT NULL default 300 CHECK (frequency = -1 OR frequency >= 5),
    powa_coalesce integer NOT NULL default 100 CHECK (powa_coalesce >= 5),
    retention interval NOT NULL default '1 day'::interval,
    allow_ui_connection boolean NOT NULL default true,
    version text,
    UNIQUE (hostname, port),
    UNIQUE(alias)
);
INSERT INTO @extschema@.powa_servers VALUES (0, '', '<local>', 0, '', NULL, '', -1, 100, '0 second');

CREATE TABLE @extschema@.powa_extensions (
    extname text NOT NULL PRIMARY KEY,
    external boolean NOT NULL default true,
    added_manually boolean NOT NULL default true
);

INSERT INTO @extschema@.powa_extensions
    (extname,              external, added_manually) VALUES
    ('powa',               false,    false),
    ('hypopg',             false,    false),
    ('pg_stat_statements', false,    false),
    ('pg_qualstats',       false,    false),
    ('pg_stat_kcache',     false,    false),
    ('pg_track_settings',  true,     false),
    ('pg_wait_sampling',   false,    false);

CREATE TABLE @extschema@.powa_extension_functions (
    extname text NOT NULL,
    operation text NOT NULL,
    function_name text NOT NULL,
    query_source text default NULL,
    query_cleanup text default NULL,
    added_manually boolean NOT NULL default true,
    priority numeric NOT NULL default 10,
    PRIMARY KEY (extname, operation, function_name),
    CHECK (operation IN ('snapshot','aggregate','purge','reset')),
    FOREIGN KEY (extname) REFERENCES @extschema@.powa_extensions(extname)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

INSERT INTO @extschema@.powa_extension_functions
    (extname,              operation,   function_name,                         query_source,                     query_cleanup,                                added_manually, priority) VALUES
    ('pg_stat_statements', 'snapshot',  'powa_databases_snapshot',             'powa_databases_src',             NULL,                                         false,          -3),
    ('pg_stat_statements', 'snapshot',  'powa_statements_snapshot',            'powa_statements_src',            NULL,                                         false,          -2),
    ('pg_stat_statements', 'aggregate', 'powa_statements_aggregate',           NULL,                             NULL,                                         false,          default),
    ('pg_stat_statements', 'purge',     'powa_statements_purge',               NULL,                             NULL,                                         false,          default),
    ('pg_stat_statements', 'purge',     'powa_databases_purge',                NULL,                             NULL,                                         false,          default),
    ('pg_stat_statements', 'reset',     'powa_statements_reset',               NULL,                             NULL,                                         false,          default),
    ('pg_qualstats',       'snapshot',  'powa_qualstats_snapshot',             'powa_qualstats_src',             'SELECT {pg_qualstats}.pg_qualstats_reset()', false,          default),
    ('pg_qualstats',       'aggregate', 'powa_qualstats_aggregate',            NULL,                             NULL,                                         false,          default),
    ('pg_qualstats',       'purge',     'powa_qualstats_purge',                NULL,                             NULL,                                         false,          default),
    ('pg_qualstats',       'reset',     'powa_qualstats_reset',                NULL,                             NULL,                                         false,          default),
    ('pg_stat_kcache',     'snapshot',  'powa_kcache_snapshot',                'powa_kcache_src',                NULL,                                         false,          -1),
    ('pg_stat_kcache',     'aggregate', 'powa_kcache_aggregate',               NULL,                             NULL,                                         false,          default),
    ('pg_stat_kcache',     'purge',     'powa_kcache_purge',                   NULL,                             NULL,                                         false,          default),
    ('pg_stat_kcache',     'reset',     'powa_kcache_reset',                   NULL,                             NULL,                                         false,          default),
    ('pg_track_settings',  'snapshot',  'pg_track_settings_snapshot_settings', 'pg_track_settings_settings_src', NULL,                                         false,          default),
    ('pg_track_settings',  'snapshot',  'pg_track_settings_snapshot_rds',      'pg_track_settings_rds_src',      NULL,                                         false,          default),
    ('pg_track_settings',  'snapshot',  'pg_track_settings_snapshot_reboot',   'pg_track_settings_reboot_src',   NULL,                                         false,          default),
    ('pg_track_settings',  'reset',     'pg_track_settings_reset',             NULL,                             NULL,                                         false,          default),
    ('pg_wait_sampling',   'snapshot',  'powa_wait_sampling_snapshot',         'powa_wait_sampling_src',         NULL,                                         false,          default),
    ('pg_wait_sampling',   'aggregate', 'powa_wait_sampling_aggregate',        NULL,                             NULL,                                         false,          default),
    ('pg_wait_sampling',   'purge',     'powa_wait_sampling_purge',            NULL,                             NULL,                                         false,          default),
    ('pg_wait_sampling',   'reset',     'powa_wait_sampling_reset',            NULL,                             NULL,                                         false,          default);

CREATE TABLE @extschema@.powa_extension_config (
    srvid integer NOT NULL,
    extname text NOT NULL,
    version text,
    enabled bool NOT NULL default true,
    added_manually boolean NOT NULL default true,
    PRIMARY KEY (srvid, extname),
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE,
    FOREIGN KEY (extname) REFERENCES @extschema@.powa_extensions(extname)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

INSERT INTO @extschema@.powa_extension_config (srvid, extname, added_manually) VALUES
    (0, 'powa', false),
    (0, 'pg_stat_statements', false);

-- This is for cluster-wide core views, we don't support custom datasources
-- here.
CREATE TABLE @extschema@.powa_modules (
    module text NOT NULL PRIMARY KEY,
    min_version integer NOT NULL DEFAULT 0
);

-- we only manually insert data in this table (and the other related tables)
-- for modules with a custom implementation.  For the rest of the modules, this
-- is done automatically with calls to powa_generic_module_setup()
INSERT INTO @extschema@.powa_modules (module) VALUES
    ('pg_database'),
    ('pg_role');

CREATE TABLE @extschema@.powa_module_config (
    srvid integer NOT NULL,
    module text NOT NULL,
    enabled bool NOT NULL default true,
    PRIMARY KEY (srvid, module),
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE,
    FOREIGN KEY (module) REFERENCES @extschema@.powa_modules(module)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

INSERT INTO @extschema@.powa_module_config (srvid, module) VALUES
    (0, 'pg_database'),
    (0, 'pg_role');

CREATE TABLE @extschema@.powa_module_functions (
    module text NOT NULL,
    operation text NOT NULL,
    function_name text NOT NULL,
    query_source text default NULL,
    PRIMARY KEY (module, operation),
    CHECK (operation IN ('snapshot','aggregate','purge','reset')),
    FOREIGN KEY (module) REFERENCES @extschema@.powa_modules (module)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

INSERT INTO @extschema@.powa_module_functions (module, operation, function_name, query_source) VALUES
    ('pg_database',      'snapshot',  'powa_catalog_database_snapshot', 'powa_catalog_database_src'),
    ('pg_database',      'reset',     'powa_catalog_database_reset',    NULL),
    ('pg_role',          'snapshot',  'powa_catalog_role_snapshot',     'powa_catalog_role_src'),
    ('pg_role',          'reset',     'powa_catalog_role_reset',        NULL);

CREATE VIEW @extschema@.powa_functions AS
    SELECT srvid, 'extension' AS kind, extname AS name, operation, external,
        function_name, query_source, query_cleanup, enabled, priority
    FROM @extschema@.powa_extensions e
    JOIN @extschema@.powa_extension_functions f USING (extname)
    JOIN @extschema@.powa_extension_config c USING (extname)
    UNION ALL
    SELECT srvid, 'module' AS kind, module AS name, operation, false,
        function_name, query_source, NULL, enabled, 100
    FROM @extschema@.powa_modules m
    JOIN @extschema@.powa_module_functions f USING (module)
    JOIN @extschema@.powa_module_config c USING (module)
    WHERE current_setting('server_version_num')::int >= m.min_version;

CREATE TABLE @extschema@.powa_db_modules (
    db_module text NOT NULL PRIMARY KEY,
    tmp_table text NOT NULL,
    external boolean NOT NULL default true,
    added_manually boolean NOT NULL default true
);

INSERT INTO @extschema@.powa_db_modules (db_module, tmp_table, external, added_manually) VALUES
    ('pg_stat_all_indexes', '@extschema@.powa_all_indexes_src_tmp', false, false),
    ('pg_stat_all_tables', '@extschema@.powa_all_tables_src_tmp', false, false),
    ('pg_stat_user_functions', '@extschema@.powa_user_functions_src_tmp', false, false);

-- No default rows for this table as this is a remote-server only feature.
-- A NULL dbnames means that the module is activated for all databases,
-- otherwise the module is only activated for the specified database names.
CREATE TABLE @extschema@.powa_db_module_config (
    srvid integer NOT NULL,
    db_module text NOT NULL,
    dbnames text[],
    enabled boolean NOT NULL default true,
    PRIMARY KEY (srvid, db_module),
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE,
    FOREIGN KEY (db_module) REFERENCES @extschema@.powa_db_modules(db_module)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

CREATE TABLE @extschema@.powa_db_module_functions (
    db_module text NOT NULL,
    operation text NOT NULL,
    function_name text NOT NULL,
    added_manually boolean NOT NULL default true,
    priority numeric NOT NULL default 10,
    CHECK (operation IN ('snapshot','aggregate','purge','reset')),
    PRIMARY KEY (db_module, operation),
    FOREIGN KEY (db_module) REFERENCES @extschema@.powa_db_modules(db_module)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

INSERT INTO @extschema@.powa_db_module_functions
    (db_module,                operation,   function_name,                   added_manually) VALUES
    ('pg_stat_all_tables',     'snapshot',  'powa_all_tables_snapshot',      false),
    ('pg_stat_all_tables',     'aggregate', 'powa_all_tables_aggregate',     false),
    ('pg_stat_all_tables',     'purge',     'powa_all_tables_purge',         false),
    ('pg_stat_all_tables',     'reset',     'powa_all_tables_reset',         false),
    ('pg_stat_all_indexes',    'snapshot',  'powa_all_indexes_snapshot',     false),
    ('pg_stat_all_indexes',    'aggregate', 'powa_all_indexes_aggregate',    false),
    ('pg_stat_all_indexes',    'purge',     'powa_all_indexes_purge',        false),
    ('pg_stat_all_indexes',    'reset',     'powa_all_indexes_reset',        false),
    ('pg_stat_user_functions', 'snapshot',  'powa_user_functions_snapshot',  false),
    ('pg_stat_user_functions', 'aggregate', 'powa_user_functions_aggregate', false),
    ('pg_stat_user_functions', 'purge',     'powa_user_functions_purge',     false),
    ('pg_stat_user_functions', 'reset',     'powa_user_functions_reset',     false);

CREATE TABLE @extschema@.powa_db_module_src_queries (
    db_module text NOT NULL,
    min_version integer NOT NULL,
    added_manually boolean NOT NULL default true,
    query_source text NOT NULL,
    PRIMARY KEY (db_module, min_version),
    FOREIGN KEY (db_module) REFERENCES @extschema@.powa_db_modules(db_module)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

INSERT INTO @extschema@.powa_db_module_src_queries
    (db_module, min_version, added_manually, query_source) VALUES
    -- pg_stat_all_tables
    ('pg_stat_all_tables', 0, false,
     'SELECT relid, pg_table_size(relid) AS tbl_size,
        seq_scan, NULL AS last_seq_scan, seq_tup_read,
        idx_scan, NULL AS last_idx_scan, idx_tup_fetch,
        n_tup_ins, n_tup_upd, n_tup_del, n_tup_hot_upd, 0 AS n_tup_newpage_upd,
        n_live_tup, n_dead_tup, n_mod_since_analyze, 0 AS n_ins_since_vacuum,
        last_vacuum, last_autovacuum, last_analyze, last_autoanalyze,
        vacuum_count, autovacuum_count, analyze_count, autoanalyze_count,
        heap_blks_read, heap_blks_hit, idx_blks_read, idx_blks_hit,
        toast_blks_read, toast_blks_hit, tidx_blks_read, tidx_blks_hit
     FROM pg_catalog.pg_stat_all_tables st
     JOIN pg_catalog.pg_statio_all_tables sit USING (relid)'),
    -- pg_stat_all_tables pg13+, n_ins_since_vacuum added
    ('pg_stat_all_tables', 130000, false,
     'SELECT relid, pg_table_size(relid) AS tbl_size,
        seq_scan, NULL AS last_seq_scan, seq_tup_read,
        idx_scan, NULL AS last_idx_scan, idx_tup_fetch,
        n_tup_ins, n_tup_upd, n_tup_del, n_tup_hot_upd, 0 AS n_tup_newpage_upd,
        n_live_tup, n_dead_tup, n_mod_since_analyze, n_ins_since_vacuum,
        last_vacuum, last_autovacuum, last_analyze, last_autoanalyze,
        vacuum_count, autovacuum_count, analyze_count, autoanalyze_count,
        heap_blks_read, heap_blks_hit, idx_blks_read, idx_blks_hit,
        toast_blks_read, toast_blks_hit, tidx_blks_read, tidx_blks_hit
     FROM pg_catalog.pg_stat_all_tables st
     JOIN pg_catalog.pg_statio_all_tables sit USING (relid)'),
    -- pg_stat_all_tables pg16+, last_seq_scan, last_idx_scan and
    -- n_tup_newpage_upd added
    ('pg_stat_all_tables', 160000, false,
     'SELECT relid, pg_table_size(relid) AS tbl_size,
        seq_scan,  last_seq_scan, seq_tup_read,
        idx_scan,  last_idx_scan, idx_tup_fetch,
        n_tup_ins, n_tup_upd, n_tup_del, n_tup_hot_upd, n_tup_newpage_upd,
        n_live_tup, n_dead_tup, n_mod_since_analyze, n_ins_since_vacuum,
        last_vacuum, last_autovacuum, last_analyze, last_autoanalyze,
        vacuum_count, autovacuum_count, analyze_count, autoanalyze_count,
        heap_blks_read, heap_blks_hit, idx_blks_read, idx_blks_hit,
        toast_blks_read, toast_blks_hit, tidx_blks_read, tidx_blks_hit
     FROM pg_catalog.pg_stat_all_tables st
     JOIN pg_catalog.pg_statio_all_tables sit USING (relid)'),
    -- pg_stat_all_indexes
    ('pg_stat_all_indexes', 0, false,
     'SELECT si.relid, indexrelid, pg_table_size(indexrelid) AS idx_size,
        idx_scan, NULL AS last_idx_scan,
        idx_tup_read, idx_tup_fetch, idx_blks_read, idx_blks_hit
      FROM pg_catalog.pg_stat_all_indexes si
      JOIN pg_catalog.pg_statio_all_indexes sit USING (indexrelid)'),
    -- pg_stat_all_indexes pg16+
    ('pg_stat_all_indexes', 160000, false,
     'SELECT si.relid, indexrelid, pg_table_size(indexrelid) AS idx_size,
        idx_scan, last_idx_scan,
        idx_tup_read, idx_tup_fetch, idx_blks_read, idx_blks_hit
      FROM pg_catalog.pg_stat_all_indexes si
      JOIN pg_catalog.pg_statio_all_indexes sit USING (indexrelid)'),
    -- pg_stat_user_functions
    ('pg_stat_user_functions', 0, false,
     'SELECT funcid, calls, total_time, self_time
      FROM pg_catalog.pg_stat_user_functions');

CREATE FUNCTION @extschema@.powa_db_functions(_srvid integer,
                                              _server_version_num integer)
RETURNS TABLE (srvid integer, db_module text, operation text,
               function_name text, dbnames text[], query_source text,
               tmp_table text, enabled bool, priority numeric)
AS $_$
    SELECT srvid, db_module, operation, function_name, dbnames, query_source,
        CASE WHEN f.operation = 'snapshot'
            THEN tmp_table
            ELSE NULL
        END AS tmp_table, enabled, priority
    FROM @extschema@.powa_db_modules m
    JOIN @extschema@.powa_db_module_functions f USING (db_module)
    JOIN @extschema@.powa_db_module_config c USING (db_module)
    LEFT JOIN LATERAL (
        SELECT query_source
        FROM @extschema@.powa_db_module_src_queries q
        WHERE q.db_module = m.db_module
        AND min_version <= _server_version_num
        ORDER BY min_version DESC
        LIMIT 1
    ) q ON f.operation = 'snapshot'
    WHERE srvid = _srvid;
$_$ LANGUAGE sql
SET search_path = pg_catalog;

CREATE VIEW @extschema@.powa_all_functions AS
    SELECT *
    FROM @extschema@.powa_functions
    UNION ALL
    SELECT srvid, 'db_module' AS kind, db_module AS name, operation, external,
        function_name, NULL as query_source, NULL AS query_cleanup, enabled,
        priority
    FROM @extschema@.powa_db_modules pdm
    JOIN @extschema@.powa_db_module_config pdmc USING (db_module)
    JOIN @extschema@.powa_db_module_functions pdmf USING (db_module);

CREATE TABLE @extschema@.powa_catalogs (
    catname text NOT NULL PRIMARY KEY,
    tmp_table text NOT NULL,
    priority numeric NOT NULL default 10
);

-- pg_class is last as we use it to record the last_refresh timestamp
INSERT INTO @extschema@.powa_catalogs
    (catname,        tmp_table,                        priority) VALUES
    ('pg_class',     'powa_catalog_class_src_tmp',     99),
    ('pg_attribute', 'powa_catalog_attribute_src_tmp', DEFAULT),
    ('pg_namespace', 'powa_catalog_namespace_src_tmp', DEFAULT),
    ('pg_type',      'powa_catalog_type_src_tmp',      DEFAULT),
    ('pg_collation', 'powa_catalog_collation_src_tmp', DEFAULT),
    ('pg_proc',      'powa_catalog_proc_src_tmp',      DEFAULT),
    ('pg_language',  'powa_catalog_language_src_tmp',  DEFAULT)
    ;

CREATE TABLE @extschema@.powa_catalog_src_queries (
    catname text NOT NULL,
    min_version integer NOT NULL,
    query_source text NOT NULL,
    PRIMARY KEY (catname, min_version),
    FOREIGN KEY (catname) REFERENCES @extschema@.powa_catalogs(catname)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

-- we exclude all temporary objects as they're unlikely to be helpful and might
-- bloat the underlying tables.
INSERT INTO @extschema@.powa_catalog_src_queries
    (catname, min_version, query_source) VALUES
    -- pg_class
    ('pg_class', 0,
     $$SELECT oid, relname::text AS relname, relnamespace, relpages, reltuples,
        reltoastrelid, relisshared, relpersistence, relkind, relnatts,
        false AS relrowsecurity, false AS relforcerowsecurity, relreplident,
        false AS relispartition,
        reloptions,
        NULL::text AS relpartbound
      FROM pg_catalog.pg_class
      WHERE relpersistence != 't'$$),
    -- pg_class 9.5+, relrowsecurity and relforcerowsecurity added
    ('pg_class', 90500,
     $$SELECT oid, relname::text AS relname, relnamespace, relpages, reltuples,
        reltoastrelid, relisshared, relpersistence, relkind, relnatts,
        relrowsecurity, relforcerowsecurity, relreplident,
        false AS relispartition,
        reloptions,
        NULL::text AS relpartbound
      FROM pg_catalog.pg_class
      WHERE relpersistence != 't'$$),
    -- pg_class pg10+, relispartition and repartbound added
    ('pg_class', 100000,
     $$SELECT oid, relname::text AS relname, relnamespace, relpages, reltuples,
        reltoastrelid, relisshared, relpersistence, relkind, relnatts,
        relrowsecurity, relforcerowsecurity, relreplident,
        relispartition,
        reloptions,
        pg_get_expr(relpartbound, oid) AS relpartbound
      FROM pg_catalog.pg_class
      WHERE relpersistence != 't'$$),
    -- pg_attribute
    ('pg_attribute', 0,
     $$SELECT attrelid, attname::text AS attname, atttypid, attlen, attnum,
        ''::"char" AS attcompression, attnotnull, atthasdef,
        false AS atthasmissing, ''::"char" AS attidentity,
        ''::"char" AS attgenerated, attstattarget, attcollation, attoptions,
        attfdwoptions
      FROM pg_catalog.pg_attribute a
      JOIN pg_catalog.pg_class c ON c.oid = a.attrelid
      WHERE a.attnum > 0
      AND NOT attisdropped
      AND c.relpersistence != 't'$$),
    -- pg_attribute pg10+, attidentity added
    ('pg_attribute', 100000,
     $$SELECT attrelid, attname::text AS attname, atttypid, attlen, attnum,
        ''::"char" AS attcompression, attnotnull, atthasdef,
        false AS atthasmissing, attidentity,
        ''::"char" AS attgenerated, attstattarget, attcollation, attoptions,
        attfdwoptions
      FROM pg_catalog.pg_attribute a
      JOIN pg_catalog.pg_class c ON c.oid = a.attrelid
      WHERE a.attnum > 0
      AND NOT attisdropped
      AND c.relpersistence != 't'$$),
    -- pg_attribute pg11+, atthasmissing added
    ('pg_attribute', 110000,
     $$SELECT attrelid, attname::text AS attname, atttypid, attlen, attnum,
        ''::"char" AS attcompression, attnotnull, atthasdef,
        atthasmissing, attidentity,
        ''::"char" AS attgenerated, attstattarget, attcollation, attoptions,
        attfdwoptions
      FROM pg_catalog.pg_attribute a
      JOIN pg_catalog.pg_class c ON c.oid = a.attrelid
      WHERE a.attnum > 0
      AND NOT attisdropped
      AND c.relpersistence != 't'$$),
    -- pg_attribute pg12+, attgenerated added
    ('pg_attribute', 120000,
     $$SELECT attrelid, attname::text AS attname, atttypid, attlen, attnum,
        ''::"char" AS attcompression, attnotnull, atthasdef,
        atthasmissing, attidentity,
        attgenerated, attstattarget, attcollation, attoptions,
        attfdwoptions
      FROM pg_catalog.pg_attribute a
      JOIN pg_catalog.pg_class c ON c.oid = a.attrelid
      WHERE a.attnum > 0
      AND NOT attisdropped
      AND c.relpersistence != 't'$$),
    -- pg_attribute pg14+, attcompression added
    ('pg_attribute', 140000,
     $$SELECT attrelid, attname::text AS attname, atttypid, attlen, attnum,
        attcompression, attnotnull, atthasdef,
        atthasmissing, attidentity,
        attgenerated, attstattarget, attcollation, attoptions,
        attfdwoptions
      FROM pg_catalog.pg_attribute a
      JOIN pg_catalog.pg_class c ON c.oid = a.attrelid
      WHERE a.attnum > 0
      AND NOT attisdropped
      AND c.relpersistence != 't'$$),
    -- pg_namespace
    ('pg_namespace', 0,
     $$SELECT oid, nspname::text AS nspname
      FROM pg_catalog.pg_namespace$$),
    -- pg_type
    ('pg_type', 0,
     $$SELECT oid, typname::text AS typname, typnamespace, typlen, typbyval,
        typtype, typcategory, typispreferred, typisdefined, typdelim,
        typrelid, typelem, typarray, typalign, typstorage, typnotnull,
        typbasetype, typtypmod, typndims, typcollation, typdefault
      FROM pg_catalog.pg_type$$),
    -- pg_collation
    ('pg_collation', 0,
     $$SELECT oid, collname::text AS collname, collnamespace,
        CASE WHEN collname = 'default' THEN 'd' ELSE 'c' END AS collprovider,
        true AS collisdeterministic,
        pg_encoding_to_char(collencoding) AS collencoding, collcollate,
        collctype,
        NULL::text AS colllocale, NULL::text AS collicurules,
        NULL::text AS collversion
      FROM pg_catalog.pg_collation$$),
    -- pg_collation pg10+, collprovider and collversion added
    ('pg_collation', 100000,
     $$SELECT oid, collname::text AS collname, collnamespace, collprovider,
        true AS collisdeterministic,
        pg_encoding_to_char(collencoding) AS collencoding, collcollate,
        collctype,
        NULL::text AS colllocale, NULL::text AS collicurules,
        collversion
      FROM pg_catalog.pg_collation$$),
    -- pg_collation pg12+, collisdeterministic added
    ('pg_collation', 120000,
     $$SELECT oid, collname::text AS collname, collnamespace, collprovider,
        collisdeterministic,
        pg_encoding_to_char(collencoding) AS collencoding, collcollate,
        collctype,
        NULL::text AS colllocale, NULL::text AS collicurules,
        collversion
      FROM pg_catalog.pg_collation$$),
    -- pg_collation pg15+, colliculocale added
    ('pg_collation', 150000,
     $$SELECT oid, collname::text AS collname, collnamespace, collprovider,
        collisdeterministic,
        pg_encoding_to_char(collencoding) AS collencoding, collcollate,
        collctype,
        colliculocale AS colllocale, NULL::text AS collicurules,
        collversion
      FROM pg_catalog.pg_collation$$),
    -- pg_collation pg16+, collicurules added
    ('pg_collation', 160000,
     $$SELECT oid, collname::text AS collname, collnamespace, collprovider,
        collisdeterministic,
        pg_encoding_to_char(collencoding) AS collencoding, collcollate,
        collctype,
        colliculocale AS colllocale, collicurules,
        collversion
      FROM pg_catalog.pg_collation$$),
    -- pg_collation pg16+, colliculocale renamed to colllocale
    ('pg_collation', 170000,
     $$SELECT oid, collname::text AS collname, collnamespace, collprovider,
        collisdeterministic,
        pg_encoding_to_char(collencoding) AS collencoding, collcollate,
        collctype,
        colllocale, collicurules,
        collversion
      FROM pg_catalog.pg_collation$$),
    -- pg_proc
    ('pg_proc', 0,
     $$SELECT oid, proname::text AS proname, oid::regprocedure AS regprocedure,
    pronamespace, prolang, procost,
        prorows, provariadic,
        CASE
            WHEN proisagg THEN 'a'::"char"
            WHEN proiswindow THEN 'w'::"char"
            ELSE 'f'::"char"
        END AS prokind, prosecdef, proleakproof, proisstrict, proretset,
        provolatile, 'u'::"char" AS proparallel, pronargs, prorettype,
        proargtypes, prosrc, proconfig
      FROM pg_catalog.pg_proc$$),
    -- pg_proc pg 9.6+, proparallel added
    ('pg_proc', 90600,
     $$SELECT oid, proname::text AS proname, oid::regprocedure AS regprocedure,
        pronamespace, prolang, procost,
        prorows, provariadic,
        CASE
            WHEN proisagg THEN 'a'::"char"
            WHEN proiswindow THEN 'w'::"char"
            ELSE 'f'::"char"
        END AS prokind, prosecdef, proleakproof, proisstrict, proretset,
        provolatile, proparallel, pronargs, prorettype,
        proargtypes,
        prosrc,
        proconfig
      FROM pg_catalog.pg_proc$$),
    -- pg_proc pg11+, prokind added replacing proisagg and proiswindow
    ('pg_proc', 110000,
     $$SELECT oid, proname::text AS proname, oid::regprocedure AS regprocedure,
        pronamespace, prolang, procost,
        prorows, provariadic,
        prokind, prosecdef, proleakproof, proisstrict, proretset,
        provolatile, proparallel, pronargs, prorettype,
        proargtypes,
        prosrc,
        proconfig
      FROM pg_catalog.pg_proc$$),
    -- pg_proc pg14+, prosqlbody added
    ('pg_proc', 140000,
     $$SELECT oid, proname::text AS proname, oid::regprocedure AS regprocedure,
        pronamespace, prolang, procost,
        prorows, provariadic,
        prokind, prosecdef, proleakproof, proisstrict, proretset,
        provolatile, proparallel, pronargs, prorettype,
        proargtypes,
        CASE WHEN prosqlbody IS NOT NULL THEN
            pg_catalog.pg_get_function_sqlbody(oid)
        ELSE
            prosrc
        END AS prosrc,
        proconfig
      FROM pg_catalog.pg_proc$$),
    -- pg_language
    ('pg_language', 0,
     $$SELECT oid, lanname::text AS lanname, lanispl, lanpltrusted
      FROM pg_catalog.pg_language$$)
    ;

CREATE FUNCTION @extschema@.powa_catalog_src_query(_catname text,
                                                   _server_version_num integer)
RETURNS text
AS $_$
    SELECT query_source
    FROM @extschema@.powa_catalog_src_queries
    WHERE catname = _catname
    AND min_version <= _server_version_num
    ORDER BY min_version DESC
    LIMIT 1;
$_$ LANGUAGE sql
SET search_path = pg_catalog;

CREATE TABLE @extschema@.powa_catalog_databases (
    srvid integer NOT NULL,
    oid oid NOT NULL,
    datname text NOT NULL,
    last_refresh timestamp with time zone,
    PRIMARY KEY (srvid, oid),
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

CREATE TABLE @extschema@.powa_catalog_roles (
    srvid integer NOT NULL,
    oid oid NOT NULL,
    rolname text NOT NULL,
    rolsuper boolean NOT NULL,
    rolinherit boolean NOT NULL,
    rolcreaterole boolean NOT NULL,
    rolcreatedb boolean NOT NULL,
    rolcanlogin boolean NOT NULL,
    rolreplication boolean NOT NULL,
    rolbypassrls boolean NOT NULL,
    PRIMARY KEY (srvid, oid),
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

CREATE FUNCTION @extschema@.powa_catalog_functions(_srvid integer,
    _server_version_num integer, _refresh_interval interval DEFAULT '1 year')
RETURNS TABLE (catname text, query_source text, tmp_table text,
               excluded_dbnames text[])
AS $_$
    SELECT catname,
        src_query AS query_source,
        '@extschema@.'
            || quote_ident('powa_catalog_'
            || replace(catname, 'pg_', '') || '_src_tmp') AS tmp_table,
        coalesce(array_agg(datname)
            FILTER (WHERE (last_refresh + _refresh_interval) >= now()), '{}')
        AS excluded_dbnames
    FROM @extschema@.powa_catalogs c
    LEFT JOIN @extschema@.powa_catalog_databases d ON d.srvid = _srvid
    LEFT JOIN LATERAL (
        SELECT @extschema@.powa_catalog_src_query(catname, _server_version_num)
    ) f(src_query) ON (true)
    GROUP BY catname, src_query;
$_$ LANGUAGE sql
SET search_path = pg_catalog;

CREATE TABLE @extschema@.powa_catalog_class (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    oid oid NOT NULL,
    relname text NOT NULL,
    relnamespace oid NOT NULL,
    relpages integer NOT NULL,
    reltuples real NOT NULL,
    reltoastrelid oid NOT NULL,
    relisshared bool NOT NULL,
    relpersistence "char" NOT NULL,
    relkind "char" NOT NULL,
    relnatts smallint NOT NULL,
    relrowsecurity boolean NOT NULL,
    relforcerowsecurity boolean NOT NULL,
    relreplident "char" NOT NULL,
    relispartition boolean NOT NULL,
    reloptions text[],
    relpartbound text,
    PRIMARY KEY (srvid, dbid, oid),
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE,
    FOREIGN KEY (srvid, dbid) REFERENCES @extschema@.powa_catalog_databases(srvid, oid)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

CREATE TABLE @extschema@.powa_catalog_attribute (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    attrelid oid NOT NULL,
    attname text NOT NULL,
    atttypid oid NOT NULL,
    attlen smallint NOT NULL,
    attnum smallint NOT NULL,
    attcompression "char" NOT NULL,
    attnotnull boolean NOT NULL,
    atthasdef boolean NOT NULL,
    atthasmissing boolean NOT NULL,
    attidentity "char" NOT NULL,
    attgenerated "char" NOT NULL,
    attstattarget smallint,
    attcollation oid NOT NULL,
    attoptions text[],
    attfdwoptions text[],
    PRIMARY KEY (srvid, dbid, attrelid, attnum),
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE,
    FOREIGN KEY (srvid, dbid) REFERENCES @extschema@.powa_catalog_databases(srvid, oid)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

CREATE TABLE @extschema@.powa_catalog_namespace (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    oid oid NOT NULL,
    nspname text NOT NULL,
    PRIMARY KEY (srvid, dbid, oid),
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE,
    FOREIGN KEY (srvid, dbid) REFERENCES @extschema@.powa_catalog_databases(srvid, oid)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

CREATE TABLE @extschema@.powa_catalog_type (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    oid oid NOT NULL,
    typname text NOT NULL,
    typnamespace oid NOT NULL,
    typlen smallint NOT NULL,
    typbyval boolean NOT NULL,
    typtype "char" NOT NULL,
    typcategory "char" NOT NULL,
    typispreferred boolean NOT NULL,
    typisdefined boolean NOT NULL,
    typdelim "char" NOT NULL,
    typrelid oid NOT NULL,
    typelem oid NOT NULL,
    typarray oid NOT NULL,
    typalign "char" NOT NULL,
    typstorage "char" NOT NULL,
    typnotnull boolean NOT NULL,
    typbasetype oid NOT NULL,
    typtypmod integer NOT NULL,
    typndims integer NOT NULL,
    typcollation oid NOT NULL,
    typdefault text,
    PRIMARY KEY (srvid, dbid, oid),
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE,
    FOREIGN KEY (srvid, dbid) REFERENCES @extschema@.powa_catalog_databases(srvid, oid)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

CREATE TABLE @extschema@.powa_catalog_collation (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    oid oid NOT NULL,
    collname text NOT NULL,
    collnamespace oid NOT NULL,
    collprovider "char" NOT NULL,
    collisdeterministic boolean NOT NULL,
    collencoding text NOT NULL,
    collcollate text,
    collctype text,
    colllocale text,
    collicurules text,
    collversion text,
    PRIMARY KEY (srvid, dbid, oid),
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE,
    FOREIGN KEY (srvid, dbid) REFERENCES @extschema@.powa_catalog_databases(srvid, oid)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

-- we also store oid::regprocedure for an easy way to have an unambiguous
-- identifier
CREATE TABLE @extschema@.powa_catalog_proc (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    oid oid NOT NULL,
    proname text NOT NULL,
    regprocedure text NOT NULL,
    pronamespace oid NOT NULL,
    prolang oid NOT NULL,
    procost real NOT NULL,
    prorows real NOT NULL,
    provariadic oid NOT NULL,
    prokind "char" NOT NULL,
    prosecdef boolean NOT NULL,
    proleakproof boolean NOT NULL,
    proisstrict boolean NOT NULL,
    proretset boolean NOT NULL,
    provolatile "char" NOT NULL,
    proparallel "char" NOT NULL,
    pronargs smallint NOT NULL,
    prorettype oid NOT NULL,
    proargtypes oidvector NOT NULL,
    prosrc text NOT NULL,
    proconfig text[],
    PRIMARY KEY (srvid, dbid, oid),
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE,
    FOREIGN KEY (srvid, dbid) REFERENCES @extschema@.powa_catalog_databases(srvid, oid)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

CREATE TABLE @extschema@.powa_catalog_language (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    oid oid NOT NULL,
    lanname text NOT NULL,
    lanispl boolean NOT NULL,
    lanpltrusted boolean NOT NULL,
    PRIMARY KEY (srvid, dbid, oid),
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE,
    FOREIGN KEY (srvid, dbid) REFERENCES @extschema@.powa_catalog_databases(srvid, oid)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

CREATE TABLE @extschema@.powa_snapshot_metas (
    srvid integer PRIMARY KEY,
    coalesce_seq bigint NOT NULL default (1),
    snapts timestamp with time zone NOT NULL default '-infinity'::timestamptz,
    aggts timestamp with time zone NOT NULL default '-infinity'::timestamptz,
    purgets timestamp with time zone NOT NULL default '-infinity'::timestamptz,
    errors text[],
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
INSERT INTO @extschema@.powa_snapshot_metas (srvid) VALUES (0);

CREATE TABLE @extschema@.powa_databases (
    srvid   integer NOT NULL,
    oid     oid,
    datname name,
    dropped timestamp with time zone,
    PRIMARY KEY (srvid, oid),
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

CREATE TABLE @extschema@.powa_statements (
    srvid integer NOT NULL,
    queryid bigint NOT NULL,
    dbid oid NOT NULL,
    userid oid NOT NULL,
    query text NOT NULL,
    last_present_ts timestamptz NULL DEFAULT now(),
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE,
    FOREIGN KEY (srvid, dbid) REFERENCES @extschema@.powa_databases(srvid, oid)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);

ALTER TABLE ONLY @extschema@.powa_statements
    ADD CONSTRAINT powa_statements_pkey PRIMARY KEY (srvid, queryid, dbid, userid);

CREATE INDEX powa_statements_dbid_idx ON @extschema@.powa_statements(srvid, dbid);
CREATE INDEX powa_statements_userid_idx ON @extschema@.powa_statements(userid);
CREATE INDEX powa_statements_mru_idx ON @extschema@.powa_statements (last_present_ts);

CREATE FUNCTION @extschema@.powa_stat_user_functions(IN dbid oid, OUT funcid oid,
    OUT calls bigint,
    OUT total_time double precision,
    OUT self_time double precision)
    RETURNS SETOF record STABLE
    LANGUAGE c COST 100
AS '$libdir/powa', 'powa_stat_user_functions';

CREATE FUNCTION @extschema@.powa_stat_all_rel(IN dbid oid,
    OUT relid oid,
    OUT numscan bigint,
    OUT tup_returned bigint,
    OUT tup_fetched bigint,
    OUT n_tup_ins bigint,
    OUT n_tup_upd bigint,
    OUT n_tup_del bigint,
    OUT n_tup_hot_upd bigint,
    OUT n_liv_tup bigint,
    OUT n_dead_tup bigint,
    OUT n_mod_since_analyze bigint,
    OUT blks_read bigint,
    OUT blks_hit bigint,
    OUT last_vacuum timestamp with time zone,
    OUT vacuum_count bigint,
    OUT last_autovacuum timestamp with time zone,
    OUT autovacuum_count bigint,
    OUT last_analyze timestamp with time zone,
    OUT analyze_count bigint,
    OUT last_autoanalyze timestamp with time zone,
    OUT autoanalyze_count bigint)
    RETURNS SETOF record STABLE
    LANGUAGE c COST 100
AS '$libdir/powa', 'powa_stat_all_rel';

-------------------------------
-- data sources generic support
-------------------------------
CREATE FUNCTION @extschema@.powa_generic_datatype_setup(
    _datasource text,
    _cols text[],
    _extra jsonb DEFAULT '{}',
    _need_operators boolean default true
)
RETURNS void AS
$$
DECLARE
    i integer;
    v_prefix text;
    v_sql text;
    v_record_name text;
    v_colname text;
    v_coltype text;
    v_extra text;
    v_kind text;
    c_no_agg text[];
    v_has_no_agg_col bool;
    c_no_minmax text[];
    v_has_no_minmax_col bool;
BEGIN
    IF quote_ident(_datasource) != _datasource THEN
        RAISE EXCEPTION '% require quoting, which is not supported',
                        _datasource;
    END IF;

    -- we don't put any fields for any of those datatypes in the *_history_db
    -- records, as aggregating them per-database or computing a rate wouldn't
    -- make sense
    c_no_agg := ARRAY['timestamp with time zone', 'timestamptz'];
    -- Similarly, don't put any field with datatypes that don't support min/max
    -- in the mins_in_range / maxs_in_range records
    c_no_minmax := ARRAY['xid', 'boolean'];

    -- we loop over the whole process in case we find some columns with
    -- some specific datatypes.  For those we need to create a *_history_db
    -- version datatype without such columns, as we can't do a per-db
    -- aggregation of such data.
    -- Similarly, we need to create a specific datatype for the min/max
    -- aggregated records for datatypes that don't support min/max.
    v_has_no_agg_col := false;
    v_has_no_minmax_col := false;
    FOREACH v_prefix IN ARRAY ARRAY['_history', '_history_db'] LOOP
        -- we only need _db version of the infrastructure if we skipped some
        -- columns
        EXIT WHEN v_prefix = '_history_db' AND NOT v_has_no_agg_col;

        v_record_name := _datasource || v_prefix || '_record';

        -- first, create a main record type
        -- as this is the first iteration over the columns, make sure that none
        -- of them require quoting
        v_sql := format('CREATE TYPE @extschema@.%I AS (
ts timestamp with time zone',
                        v_record_name);
        FOR i IN 1..array_upper(_cols, 1) LOOP
            v_colname := _cols[i][1];
            v_coltype := _cols[i][2];

            -- the *_history_db type version is the same as the *_history
            -- without the fields of datatype we don't aggregate
            CONTINUE WHEN v_prefix = '_history_db'
                     AND v_coltype = ANY (c_no_agg);

            IF quote_ident(v_colname) != v_colname THEN
                RAISE EXCEPTION '% require quoting, which is not supported',
                                v_colname;
            END IF;

            -- datasources should only use a few of native system types
            IF v_coltype NOT IN ('timestamp with time zone', 'oid',  'bigint',
                                 'integer', 'numeric', 'double precision',
                                 'text', 'inet', 'xid', 'pg_lsn', 'interval',
                                 'boolean')
            THEN
                RAISE EXCEPTION 'invalid data type % for col %.%',
                                v_coltype, _datasource, v_colname;
            END IF;

            IF v_coltype = ANY (c_no_minmax) THEN
                v_has_no_minmax_col := true;
            END IF;

            v_sql := v_sql || ',' || chr(10) || v_colname || ' ' || v_coltype;
        END LOOP;
        v_sql := v_sql || ')';
        EXECUTE v_sql;

        -- Create a specific min/max record if needed
        IF v_prefix = '_history' AND v_has_no_minmax_col THEN
            v_sql := format('CREATE TYPE @extschema@.%I AS (
ts timestamp with time zone',
                            v_record_name || '_minmax');
            FOR i IN 1..array_upper(_cols, 1) LOOP
                v_colname := _cols[i][1];
                v_coltype := _cols[i][2];

                CONTINUE WHEN v_coltype = ANY (c_no_minmax);

                v_sql := v_sql || ',' || chr(10) || v_colname || ' ' || v_coltype;
            END LOOP;
            v_sql := v_sql || ')';
            EXECUTE v_sql;
        END IF;

        CONTINUE WHEN NOT _need_operators;

        -- add a *history_rate and a *history_diff type, and remember if we saw
        -- (and skipped) any timestamptz column
        FOREACH v_kind IN ARRAY ARRAY['diff', 'rate'] LOOP
            CONTINUE WHEN v_prefix = '_history_db';

            IF v_kind = 'diff' THEN
                v_extra := 'intvl interval';
            ELSE
                v_extra := 'sec integer';
            END IF;
            v_sql := format('CREATE TYPE @extschema@.%I AS ('
                            || chr(10)
                            || '%s',
                            _datasource || v_prefix || '_' || v_kind, v_extra);
            FOR i IN 1..array_upper(_cols, 1) LOOP
                v_colname := _cols[i][1];
                v_coltype := _cols[i][2];

                IF v_coltype = ANY (c_no_agg)
                THEN
                    v_has_no_agg_col = true;
                    CONTINUE;
                END IF;

                v_colname = coalesce(_extra->v_kind->'colname'->>v_colname,
                                     v_colname);

                IF v_kind = 'rate' THEN
                    v_colname := v_colname ||
                        coalesce(_extra->v_kind->'suffix'->>v_colname,
                                 '_per_sec');
                    IF v_coltype != 'numeric' THEN
                        v_coltype := 'double precision';
                    END IF;
                END IF;

                v_sql := v_sql || ', ' || chr(10)
                    || quote_ident(v_colname) || ' ' || v_coltype;
            END LOOP;
            v_sql := v_sql || ')';
            EXECUTE v_sql;
        END LOOP;

        -- add a *_mi() function
        v_sql := format('CREATE FUNCTION @extschema@.%1$I(
a @extschema@.%2$I,
b @extschema@.%2$I)
RETURNS @extschema@.%3$I AS
$_$
DECLARE
    res @extschema@.%3$I;
BEGIN
    res.intvl = a.ts - b.ts;',
                        _datasource || v_prefix || '_mi',
                        v_record_name,
                        -- the datatype is the same for the _db version
                        _datasource || '_history_diff');
        FOR i in 1..array_upper(_cols, 1) LOOP
            CONTINUE WHEN _cols[i][2] = ANY (c_no_agg);
            v_colname := _cols[i][1];
            v_colname = coalesce(_extra->'mi'->'colname'->>v_colname,
                                 v_colname);

            v_sql := v_sql || chr(10)
                || format('    res.%1$I = a.%1$I - b.%1$I;', v_colname);
        END LOOP;
        v_sql := v_sql || chr(10) || chr(10) || '    return res;'
            || chr(10) || 'END;' || chr(10) || '$_$'
            || chr(10) || 'LANGUAGE plpgsql IMMUTABLE STRICT';
        EXECUTE v_sql;

        -- add a "-" operator
        v_sql := format('CREATE OPERATOR @extschema@.- ('
                        'PROCEDURE = @extschema@.%1$I,'
                        'LEFTARG = @extschema@.%2$I,'
                        'RIGHTARG = @extschema@.%2$I)',
                        _datasource || v_prefix || '_mi',
                        v_record_name);
        EXECUTE v_sql;

        -- add a *_div() function
        v_sql := format('CREATE FUNCTION @extschema@.%1$I(
a @extschema@.%2$I,
b @extschema@.%2$I)
RETURNS @extschema@.%3$I AS
$_$
DECLARE
    res @extschema@.%3$I;
    sec integer;
BEGIN
    res.sec = extract(EPOCH FROM (a.ts - b.ts));
    IF res.sec = 0 THEN
        sec = 1;
    ELSE
        sec = res.sec;
    END IF;',
                        _datasource || v_prefix || '_div',
                        v_record_name,
                        -- the datatype is the same for the _db version
                        _datasource || '_history_rate');
        FOR i in 1..array_upper(_cols, 1) LOOP
            CONTINUE WHEN _cols[i][2] = ANY (c_no_agg);

            v_colname := _cols[i][1];
            v_extra := coalesce(_extra->'rate'->'colname'->>v_colname,
                                v_colname);
            v_extra := v_extra ||
                coalesce(_extra->'rate'->'suffix'->>v_colname, '_per_sec');

            v_sql := v_sql || chr(10)
                || format('    res.%1$I = (a.%2$I - b.%2$I)::double precision / sec;',
                          v_extra, v_colname);
    END LOOP;
    v_sql := v_sql || '

    return res;
END;
$_$
LANGUAGE plpgsql IMMUTABLE STRICT';
    EXECUTE v_sql;

    -- add a "/" operator
    v_sql := format('CREATE OPERATOR @extschema@./ ('
                    'PROCEDURE = @extschema@.%1$I,'
                    'LEFTARG = @extschema@.%2$I,'
                    'RIGHTARG = @extschema@.%2$I)',
                    _datasource || v_prefix || '_div',
                    v_record_name);
    EXECUTE v_sql;
    END LOOP;
END;
$$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_generic_datatype_setup */

CREATE FUNCTION @extschema@.powa_generic_module_setup(_pg_module text,
                                                      _counter_cols text[],
                                                      _nullable text[] DEFAULT '{}',
                                                      _need_operators boolean default true,
                                                      _key_cols text[] DEFAULT '{}',
                                                      _key_nullable boolean DEFAULT false,
                                                      _min_version integer DEFAULT 0)
RETURNS void AS
$$
DECLARE
    i integer;
    v_nb integer;
    v_module text;
    v_sql text;
    v_colname text;
    v_coltype text;
    v_kind text;
    v_null text;
    v_has_no_minmax_col bool;
    v_suffix text;
    v_accum text;
BEGIN
    IF quote_ident(_pg_module) != _pg_module THEN
        RAISE EXCEPTION '% require quoting, which is not supported',
                         _pg_module;
    END IF;

    IF _pg_module !~ '^pg_' THEN
        RAISE EXCEPTION '% is not a postgres module', _pg_module;
    END IF;

    v_module := regexp_replace(_pg_module, '^pg', 'powa');

    -- declare the module and its configuration
    INSERT INTO @extschema@.powa_modules VALUES (_pg_module, _min_version);
    INSERT INTO @extschema@.powa_module_config VALUES (0, _pg_module);
    INSERT INTO @extschema@.powa_module_functions VALUES
        (_pg_module, 'snapshot',  v_module || '_snapshot',  v_module || '_src'),
        (_pg_module, 'aggregate', v_module || '_aggregate', NULL),
        (_pg_module, 'purge',     v_module || '_purge',     NULL),
        (_pg_module, 'reset',     v_module || '_reset',     NULL);

    -- create the underlying record datatype(s) and operators if needed
    EXECUTE @extschema@.powa_generic_datatype_setup(v_module, _counter_cols,
                                                    _need_operators => _need_operators);
    -- create the *_src_tmp unlogged table
    v_sql := format('CREATE UNLOGGED TABLE @extschema@.%I (
    srvid integer NOT NULL,
    ts timestamp with time zone NOT NULL',
                    v_module || '_src_tmp');

    -- iterate over the key columns first
    IF _key_nullable THEN
        v_null := '';
    ELSE
        v_null := ' NOT NULL';
    END IF;
    FOR i IN 1..coalesce(array_upper(_key_cols, 1), 0) LOOP
        v_colname := _key_cols[i][1];
        v_coltype := _key_cols[i][2];

        -- as this is the first iteration over the columns, make sure that none
        -- of them require quoting
        IF quote_ident(v_colname) != v_colname THEN
            RAISE EXCEPTION '% require quoting, which is not supported',
                            v_colname;
        END IF;

        -- key columns should only use a few of native system types
        IF v_coltype NOT IN ('boolean', 'integer', 'name', 'oid', 'text')
        THEN
            RAISE EXCEPTION 'invalid data type % for key col %.%',
                            v_coltype, v_module, v_colname;
        END IF;

        IF v_colname = ANY (_nullable) THEN
            RAISE EXCEPTION 'invalid nullable info for key col %.%',
                            v_module, v_colname;
        END IF;

        v_sql := v_sql || ',' || chr(10) || format('    %I %s%s',
                                                   v_colname, v_coltype, v_null);
    END LOOP;

    -- then iterate over the counter columns
    v_has_no_minmax_col := false;
    FOR i IN 1..array_upper(_counter_cols, 1) LOOP
        v_colname := _counter_cols[i][1];
        v_coltype := _counter_cols[i][2];

        IF v_coltype IN ('xid', 'boolean') THEN
            v_has_no_minmax_col := true;
        END IF;

        -- as this is the first iteration over the columns, make sure that none
        -- of them require quoting
        IF quote_ident(v_colname) != v_colname THEN
            RAISE EXCEPTION '% require quoting, which is not supported',
                            v_colname;
        END IF;

        -- datasources should only use a few of native system types
        IF v_coltype NOT IN ('timestamp with time zone', 'oid',  'bigint',
                             'integer', 'numeric', 'double precision',
                             'text', 'inet', 'xid', 'pg_lsn', 'interval',
                             'boolean')
        THEN
            RAISE EXCEPTION 'invalid data type % for col %.%',
                            v_coltype, v_module, v_colname;
        END IF;

        IF v_colname = ANY (_nullable) THEN
            _nullable := array_remove(_nullable, v_colname);
            v_null := '';
        ELSE
            v_null := ' NOT NULL';
        END IF;
        v_sql := v_sql || ',' || chr(10) || format('    %I %s%s',
                                                   v_colname, v_coltype,
                                                   v_null);
    END LOOP;

    IF array_upper(_nullable, 1) IS NOT NULL THEN
        RAISE EXCEPTION 'Columns % declared as nullable, but not found in the '
                        'list of columns', _nullable;
    END IF;

    v_sql := v_sql || ')';
    EXECUTE v_sql;

    -- create the *_history table and its index
    v_suffix := v_module || '_history_record';
    IF v_has_no_minmax_col THEN
        v_suffix := v_suffix || '_minmax';
    END IF;
    v_sql := format('CREATE TABLE @extschema@.%1$I (
    srvid integer NOT NULL,', v_module || '_history');

    IF _key_nullable THEN
        v_null := '';
    ELSE
        v_null := ' NOT NULL';
    END IF;
    FOR i IN 1..coalesce(array_upper(_key_cols, 1), 0) LOOP
        v_sql := v_sql || format('
    %I %s%s,', _key_cols[i][1], _key_cols[i][2], v_null);
    END LOOP;

    v_sql := v_sql || format('
    coalesce_range tstzrange NOT NULL,
    records @extschema@.%2$I[] NOT NULL,
    mins_in_range @extschema@.%3$I NOT NULL,
    maxs_in_range @extschema@.%3$I NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
ALTER TABLE @extschema@.%1$I ALTER COLUMN mins_in_range SET STORAGE MAIN;
ALTER TABLE @extschema@.%1$I ALTER COLUMN maxs_in_range SET STORAGE MAIN;
CREATE INDEX %4$I ON @extschema@.%1$I USING gist(srvid, coalesce_range);',
                    v_module || '_history', v_module || '_history_record',
                    v_suffix, v_module || '_history_ts');
    EXECUTE v_sql;

    -- and the *_history_current table and index
    v_accum = 'srvid integer NOT NULL,';
    IF _key_nullable THEN
        v_null := '';
    ELSE
        v_null := ' NOT NULL';
    END IF;
    FOR i IN 1..coalesce(array_upper(_key_cols, 1), 0) LOOP
        v_colname := _key_cols[i][1];
        v_coltype := _key_cols[i][2];

        v_accum := v_accum || format('
    %I %s%s,', v_colname, v_coltype, v_null);
    END LOOP;
    v_sql := format('CREATE TABLE @extschema@.%1$I (
    %3$s
    record @extschema@.%2$I NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX ON @extschema@.%1$I (srvid);',
    v_module || '_history_current', v_module || '_history_record', v_accum);
    EXECUTE v_sql;

    -- make sure the *_history and  *_history_current tables are dumped
    PERFORM pg_catalog.pg_extension_config_dump('@extschema@.' || v_module || '_history','');
    PERFORM pg_catalog.pg_extension_config_dump('@extschema@.' || v_module || '_history_current','');

    -- create the *_snapshot function
    v_accum := '_srvid';
    FOR i IN 1..coalesce(array_upper(_key_cols, 1), 0) LOOP
        v_colname := _key_cols[i][1];

        v_accum := v_accum || format(', %I', v_colname);
    END LOOP;
    v_sql := format('CREATE FUNCTION @extschema@.%1$I (_srvid integer) RETURNS void AS $PROC$
DECLARE
    result boolean;
    v_funcname    text := format(''@extschema@.%%I(%%s)'',
                                 %1$L, _srvid);
    v_rowcount    bigint;
BEGIN
    PERFORM @extschema@.powa_log(format(''running %%s'', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    -- Insert background writer statistics
    WITH rel AS (
        SELECT *
        FROM @extschema@.%2$I(_srvid)
    )
    INSERT INTO @extschema@.%3$I
        SELECT %4$s,
        ROW(ts',
                  v_module || '_snapshot',
                  v_module || '_src',
                  v_module || '_history_current',
                  v_accum);

    FOR i IN 1..array_upper(_counter_cols, 1) LOOP
        v_colname := _counter_cols[i][1];

        IF i > 1 AND (i - 1) % 3 = 0 THEN
            v_sql := v_sql || ',' || chr(10) || '            ';
        ELSE
            v_sql := v_sql || ', ';
        END IF;
        v_sql := v_sql || v_colname;
    END LOOP;

    v_sql := v_sql || format(')::@extschema@.%I AS record
        FROM rel;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format(''%%s - rowcount: %%s'',
            v_funcname, v_rowcount));

    IF (_srvid != 0) THEN
        DELETE FROM @extschema@.%I WHERE srvid = _srvid;
    END IF;

    result := true;
END;
$PROC$ language plpgsql;',
                    v_module || '_history_record', v_module || '_src_tmp');
    EXECUTE v_sql;

    -- create the *_aggregate function
    v_accum := 'srvid';
    FOR i IN 1..coalesce(array_upper(_key_cols, 1), 0) LOOP
        v_colname := _key_cols[i][1];
        v_coltype := _key_cols[i][2];

        v_sql := v_sql || format(',
            (record).%I', v_colname);

        v_accum := v_accum || ', ' || v_colname;
    END LOOP;
    v_sql := format('CREATE FUNCTION @extschema@.%1$I(_srvid integer)
RETURNS void AS $PROC$
DECLARE
    v_funcname    text := format(''@extschema@.%%I(%%s)'',
                                 %1$L, _srvid);
    v_rowcount    bigint;
BEGIN
    PERFORM @extschema@.powa_log(format(''running %%s'', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    -- aggregate %3$s history table
    INSERT INTO @extschema@.%2$I
        SELECT %4$s,
            tstzrange(min((record).ts), max((record).ts),''[]''),
            array_agg(record)',
                    v_module || '_aggregate', v_module || '_history',
                    v_module, v_accum);

    FOREACH v_kind IN ARRAY ARRAY['min', 'max'] LOOP
        v_sql := v_sql || format(',
            ROW(%s((record).ts)', v_kind);

        FOR i IN 1..array_upper(_counter_cols, 1) LOOP
            v_colname := _counter_cols[i][1];
            v_coltype := _counter_cols[i][2];

            IF v_coltype NOT IN ('xid', 'boolean') THEN
                v_sql := v_sql || format(',
                    %s((record).%I)', v_kind, v_colname);
            END IF;
        END LOOP;

        v_sql := v_sql || format(')::@extschema@.%I',
                                 v_suffix);
    END LOOP;

    v_sql := v_sql || format('
        FROM @extschema@.%1$I
        WHERE srvid = _srvid
        GROUP BY %2$s;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format(''%%s - rowcount: %%s'',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.%1$I WHERE srvid = _srvid;
 END;
$PROC$ LANGUAGE plpgsql',
                    v_module || '_history_current', v_accum);
    EXECUTE v_sql;

    -- create the *_purge function
    v_sql := format('CREATE FUNCTION @extschema@.%1$I(_srvid integer)
RETURNS void AS $PROC$
DECLARE
    v_funcname    text := format(''@extschema@.%%I(%%s)'',
                                 %1$L, _srvid);
    v_rowcount    bigint;
    v_retention   interval;
BEGIN
    PERFORM @extschema@.powa_log(format(''running %%s'', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    SELECT @extschema@.powa_get_server_retention(_srvid) INTO v_retention;

    -- Delete obsolete datas. We only bother with already coalesced data
    DELETE FROM @extschema@.%2$I
    WHERE upper(coalesce_range)< (now() - v_retention)
    AND srvid = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format(''%%s - rowcount: %%s'',
            v_funcname, v_rowcount));
END;
$PROC$ LANGUAGE plpgsql
        ',
                    v_module || '_purge', v_module || '_history');
    EXECUTE v_sql;

    -- create the *_reset function
    v_sql := format('CREATE FUNCTION @extschema@.%1$I(_srvid integer)
RETURNS boolean AS $function$
BEGIN
    PERFORM @extschema@.powa_log(''Resetting %2$I('' || _srvid || '')'');
    DELETE FROM @extschema@.%2$I WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log(''Resetting %3$I('' || _srvid || '')'');
    DELETE FROM @extschema@.%3$I WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log(''Resetting %4$I('' || _srvid || '')'');
    DELETE FROM @extschema@.%4$I WHERE srvid = _srvid;

    RETURN true;
END;
$function$ LANGUAGE plpgsql',
                    v_module || '_reset', v_module || '_history',
                    v_module || '_history_current', v_module || '_src_tmp');
    EXECUTE v_sql;
END;
$$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_generic_module_setup */

SELECT @extschema@.powa_generic_datatype_setup('powa_statements',
$${
{calls, bigint}, {total_exec_time, double precision}, {rows, bigint},
{shared_blks_hit, bigint}, {shared_blks_read, bigint},
{shared_blks_dirtied, bigint}, {shared_blks_written, bigint},
{local_blks_hit, bigint}, {local_blks_read, bigint},
{local_blks_dirtied, bigint}, {local_blks_written, bigint},
{temp_blks_read, bigint}, {temp_blks_written, bigint},
{shared_blk_read_time, double precision}, {shared_blk_write_time, double precision},
{local_blk_read_time, double precision}, {local_blk_write_time, double precision},
{temp_blk_read_time, double precision}, {temp_blk_write_time, double precision},
{plans, bigint}, {total_plan_time, double precision},
{wal_records, bigint}, {wal_fpi, bigint}, {wal_bytes, numeric},
{jit_functions, bigint}, {jit_generation_time, double precision},
{jit_inlining_count, bigint}, {jit_inlining_time, double precision},
{jit_optimization_count, bigint}, {jit_optimization_time, double precision},
{jit_emission_count, bigint}, {jit_emission_time, double precision},
{jit_deform_count, bigint}, {jit_deform_time, double precision}
}$$,
'{"rate": {
    "colname": { "total_exec_time": "runtime", "total_plan_time": "plantime"}
}}');

SELECT @extschema@.powa_generic_datatype_setup('powa_user_functions',
$${
{calls, bigint}, {total_time, double precision}, {self_time, double precision}
}$$);

-- powa_all_indexes combines info from pg_stat_all_indexes and
-- pg_statio_all_indexes
SELECT @extschema@.powa_generic_datatype_setup('powa_all_indexes',
$${
{idx_size, bigint},
{idx_scan, bigint}, {last_idx_scan, timestamp with time zone},
{idx_tup_read, bigint}, {idx_tup_fetch, bigint},
{idx_blks_read, bigint}, {idx_blks_hit, bigint}
}$$);

-- powa_all_tablees combines info from pg_stat_all_tablees and
-- pg_statio_all_tablees
SELECT @extschema@.powa_generic_datatype_setup('powa_all_tables',
$${
{tbl_size, bigint},
{seq_scan, bigint}, {last_seq_scan, timestamp with time zone},
{seq_tup_read, bigint}, {idx_scan, bigint},
{last_idx_scan, timestamp with time zone},
{n_tup_ins, bigint}, {n_tup_upd, bigint}, {n_tup_del, bigint},
{n_tup_hot_upd, bigint}, {n_tup_newpage_upd, bigint},
{n_liv_tup, bigint}, {n_dead_tup, bigint},
{n_mod_since_analyze, bigint}, {n_ins_since_vacuum, bigint},
{last_vacuum, timestamp with time zone},
{last_autovacuum, timestamp with time zone},
{last_analyze, timestamp with time zone},
{last_autoanalyze, timestamp with time zone},
{vacuum_count, bigint}, {autovacuum_count, bigint},
{analyze_count, bigint}, {autoanalyze_count, bigint},
{heap_blks_read, bigint}, {heap_blks_hit, bigint},
{idx_blks_read, bigint}, {idx_blks_hit, bigint},
{toast_blks_read, bigint}, {toast_blks_hit, bigint},
{tidx_blks_read, bigint}, {tidx_blks_hit, bigint}
}$$);

SELECT @extschema@.powa_generic_module_setup('pg_replication_slots',
$${
{cur_txid, xid}, {current_lsn, pg_lsn},
{active, boolean}, {active_pid, integer},
{slot_xmin, xid}, {catalog_xmin, xid}, {restart_lsn, pg_lsn},
{confirmed_flush_lsn, pg_lsn}, {wal_status, text}, {safe_wal_size, bigint},
{two_phase, boolean}, {conflicting, boolean}
}$$,
$${
cur_txid, active, active_pid,
slot_xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status,
safe_wal_size, two_phase, conflicting
}$$,
_key_cols => $${
{slot_name, text}, {plugin, text}, {slot_type, text}, {datoid, oid},
{temporary, boolean}
}$$,
_key_nullable => true,
_min_version => 130000
);

SELECT @extschema@.powa_generic_module_setup('pg_stat_activity',
$${
{cur_txid, xid},
{datid, oid}, {pid, integer}, {leader_pid, integer}, {usesysid, oid},
{application_name, text}, {client_addr, inet},
{backend_start, timestamp with time zone},
{xact_start, timestamp with time zone},
{query_start, timestamp with time zone},
{state_change, timestamp with time zone},
{state, text}, {backend_xid, xid}, {backend_xmin, xid},
{query_id, bigint}, {backend_type, text}
}$$,
$${
cur_txid, datid, leader_pid, usesysid, client_addr, xact_start, query_start,
state_change, state, backend_xid, backend_xmin, query_id, backend_type
}$$,
_need_operators => false);

SELECT @extschema@.powa_generic_module_setup('pg_stat_archiver',
$${
{current_wal, text},
{archived_count, bigint}, {last_archived_wal, text},
{last_archived_time, timestamp with time zone},
{failed_count, bigint},
{last_failed_wal, text}, {last_failed_time, timestamp with time zone}
}$$,
$${
current_wal, last_archived_wal, last_archived_time, last_failed_wal,
last_failed_time
}$$);

SELECT @extschema@.powa_generic_module_setup('pg_stat_bgwriter',
$${
{buffers_clean, bigint}, {maxwritten_clean, bigint},
{buffers_backend, bigint}, {buffers_backend_fsync, bigint},
{buffers_alloc, bigint}
}$$);

SELECT @extschema@.powa_generic_module_setup('pg_stat_checkpointer',
$${
{num_timed, bigint}, {num_requested, bigint},
{write_time, double precision}, {sync_time, double precision},
{buffers_written, bigint}
}$$);

SELECT @extschema@.powa_generic_module_setup('pg_stat_database',
$${
{numbackends, integer},
{xact_commit, bigint}, {xact_rollback, bigint},
{blks_read, bigint}, {blks_hit, bigint},
{tup_returned, bigint}, {tup_fetched, bigint}, {tup_inserted, bigint},
{tup_updated, bigint}, {tup_deleted, bigint},
{conflicts, bigint}, {temp_files, bigint}, {temp_bytes, bigint},
{deadlocks, bigint},
{checksum_failures, bigint}, {checksum_last_failure, timestamp with time zone},
{blk_read_time, double precision}, {blk_write_time, double precision},
{session_time, double precision}, {active_time, double precision},
{idle_in_transaction_time, double precision}, {sessions, bigint},
{sessions_abandoned, bigint}, {sessions_fatal, bigint},
{sessions_killed, bigint}, {stats_reset, timestamp with time zone}
}$$,
$${
checksum_failures, checksum_last_failure, session_time, active_time,
idle_in_transaction_time, sessions, sessions_abandoned, sessions_fatal,
sessions_killed, stats_reset
}$$,
_key_cols => $${
{datid, oid}
}$$);

SELECT @extschema@.powa_generic_module_setup('pg_stat_database_conflicts',
$${
{confl_tablespace, bigint}, {confl_lock, bigint}, {confl_snapshot, bigint},
{confl_bufferpin, bigint}, {confl_deadlock, bigint},
{confl_active_logicalslot, bigint}
}$$,
_key_cols => $${
{datid, oid}
}$$);

SELECT @extschema@.powa_generic_module_setup('pg_stat_io',
$${
{reads, bigint}, {read_time, double precision},
{writes, bigint}, {write_time, double precision},
{writebacks, bigint}, {writeback_time, double precision},
{extends, bigint}, {extend_time, double precision},
{op_bytes, bigint}, {hits, bigint}, {evictions, bigint}, {reuses, bigint},
{fsyncs, bigint}, {fsync_time, double precision},
{stats_reset, timestamp with time zone}
}$$,
$${
reads, read_time, writebacks, writeback_time, extends, extend_time, hits,
evictions, reuses, fsyncs, fsync_time
}$$,
_key_cols => $${
{backend_type, text}, {object, text}, {context, text}
}$$);

SELECT @extschema@.powa_generic_module_setup('pg_stat_replication',
$${
{current_lsn, pg_lsn},
{pid, integer}, {usename, text}, {application_name, text}, {client_addr, inet},
{backend_start, timestamp with time zone}, {backend_xmin, xid}, {state, text},
{sent_lsn, pg_lsn},
{write_lsn, pg_lsn}, {flush_lsn, pg_lsn}, {replay_lsn, pg_lsn},
{write_lag, interval}, {flush_lag, interval}, {replay_lag, interval},
{sync_priority, integer}, {sync_state, text},
{reply_time, timestamp with time zone}
}$$,
$${
pid, usename, application_name, client_addr, backend_start, backend_xmin,
state, sent_lsn, write_lsn, flush_lsn, replay_lsn, write_lag, flush_lag,
replay_lag, sync_priority, sync_state, reply_time
}$$,
_min_version => 130000
);

SELECT @extschema@.powa_generic_module_setup('pg_stat_slru',
$${
{blks_zeroed, bigint}, {blks_hit, bigint}, {blks_read, bigint},
{blks_written, bigint}, {blks_exists, bigint},
{flushes, bigint}, {truncates, bigint},
{stats_reset, timestamp with time zone}
}$$,
_key_cols => $${
{name, text}
}$$);

SELECT @extschema@.powa_generic_module_setup('pg_stat_subscription',
$${
{worker_type, text}, {pid, integer}, {leader_pid, integer}, {relid, oid},
{received_lsn, pg_lsn}, {last_msg_send_time, timestamp with time zone},
{last_msg_receipt_time, timestamp with time zone},
{latest_end_lsn, pg_lsn}, {latest_end_time, timestamp with time zone}
}$$,
$${
worker_type, pid, leader_pid, relid, received_lsn, last_msg_send_time,
last_msg_receipt_time, latest_end_lsn, latest_end_time
}$$,
_key_cols => $${
{subid, oid}, {subname, name}
}$$,
_min_version => 130000
);

-- we don't save subname, it can be found in pg_stat_subscription
SELECT @extschema@.powa_generic_module_setup('pg_stat_subscription_stats',
$${
{apply_error_count, bigint}, {sync_error_count, bigint},
{stats_reset, timestamp with time zone}
}$$,
$${
stats_reset
}$$,
_key_cols => $${
{subid, oid}
}$$);

SELECT @extschema@.powa_generic_module_setup('pg_stat_wal',
$${
{wal_records, bigint}, {wal_fpi, bigint}, {wal_bytes, numeric},
{wal_buffers_full, bigint}, {wal_write, bigint}, {wal_sync, bigint},
{wal_write_time, double precision}, {wal_sync_time, double precision},
{stats_reset, timestamp with time zone}
}$$);

SELECT @extschema@.powa_generic_module_setup('pg_stat_wal_receiver',
$${
{pid, integer}, {status, text},
{receive_start_lsn, pg_lsn}, {receive_start_tli, integer},
{last_received_lsn, pg_lsn},
{written_lsn, pg_lsn}, {flushed_lsn, pg_lsn},
{received_tli, integer},
{last_msg_send_time, timestamp with time zone},
{last_msg_receipt_time, timestamp with time zone},
{latest_end_lsn, pg_lsn}, {latest_end_time, timestamp with time zone},
{conninfo, text}
}$$,
_key_cols => $${
{slot_name, text}, {sender_host, text}, {sender_port, integer}
}$$,
_key_nullable => true,
_min_version => 130000
);

SELECT @extschema@.powa_generic_datatype_setup('powa_kcache',
$${
{plan_reads, bigint}, {plan_writes, bigint},
{plan_user_time, double precision}, {plan_system_time, double precision},
{plan_minflts, bigint}, {plan_majflts, bigint},
{plan_nswaps, bigint},
{plan_msgsnds, bigint}, {plan_msgrcvs, bigint},
{plan_nsignals, bigint},
{plan_nvcsws, bigint}, {plan_nivcsws, bigint},
{exec_reads, bigint}, {exec_writes, bigint},
{exec_user_time, double precision}, {exec_system_time, double precision},
{exec_minflts, bigint}, {exec_majflts, bigint},
{exec_nswaps, bigint},
{exec_msgsnds, bigint}, {exec_msgrcvs, bigint},
{exec_nsignals, bigint},
{exec_nvcsws, bigint}, {exec_nivcsws, bigint}
}$$);

SELECT @extschema@.powa_generic_datatype_setup('powa_qualstats',
$${
{occurences, bigint}, {execution_count, bigint}, {nbfiltered, bigint},
{mean_err_estimate_ratio, double precision},
{mean_err_estimate_num, double precision}
}$$,
'{"rate": {"suffix": {"mean_err_estimate_ratio": "",
                      "mean_err_estimate_num": ""}}}');

SELECT @extschema@.powa_generic_datatype_setup('powa_wait_sampling',
$${
{count, bigint}
}$$);

DROP FUNCTION @extschema@.powa_generic_datatype_setup(text, text[], jsonb, boolean);
DROP FUNCTION @extschema@.powa_generic_module_setup(text, text[], text[], boolean, text[], boolean, integer);

/* pg_catalog import support */
CREATE UNLOGGED TABLE @extschema@.powa_catalog_class_src_tmp (
    LIKE @extschema@.powa_catalog_class
);

CREATE UNLOGGED TABLE @extschema@.powa_catalog_attribute_src_tmp (
    LIKE @extschema@.powa_catalog_attribute
);

CREATE UNLOGGED TABLE @extschema@.powa_catalog_namespace_src_tmp (
    LIKE @extschema@.powa_catalog_namespace
);

CREATE UNLOGGED TABLE @extschema@.powa_catalog_type_src_tmp (
    LIKE @extschema@.powa_catalog_type
);

CREATE UNLOGGED TABLE @extschema@.powa_catalog_collation_src_tmp (
    LIKE @extschema@.powa_catalog_collation
);

CREATE UNLOGGED TABLE @extschema@.powa_catalog_proc_src_tmp (
    LIKE @extschema@.powa_catalog_proc
);

CREATE UNLOGGED TABLE @extschema@.powa_catalog_language_src_tmp (
    LIKE @extschema@.powa_catalog_language
);
/* end of pg_catalog import support */


CREATE UNLOGGED TABLE @extschema@.powa_databases_src_tmp (
    srvid integer NOT NULL,
    oid oid NOT NULL,
    datname name NOT NULL
);

CREATE UNLOGGED TABLE @extschema@.powa_statements_src_tmp (
    srvid integer NOT NULL,
    ts  timestamp with time zone NOT NULL,
    userid oid NOT NULL,
    dbid oid NOT NULL,
    toplevel boolean NOT NULL,
    queryid bigint NOT NULL,
    query text NOT NULL,
    calls bigint NOT NULL,
    total_exec_time double precision NOT NULL,
    rows bigint NOT NULL,
    shared_blks_hit bigint NOT NULL,
    shared_blks_read bigint NOT NULL,
    shared_blks_dirtied bigint NOT NULL,
    shared_blks_written bigint NOT NULL,
    local_blks_hit bigint NOT NULL,
    local_blks_read bigint NOT NULL,
    local_blks_dirtied bigint NOT NULL,
    local_blks_written bigint NOT NULL,
    temp_blks_read bigint NOT NULL,
    temp_blks_written bigint NOT NULL,
    shared_blk_read_time double precision NOT NULL,
    shared_blk_write_time double precision NOT NULL,
    local_blk_read_time double precision NOT NULL,
    local_blk_write_time double precision NOT NULL,
    temp_blk_read_time double precision NOT NULL,
    temp_blk_write_time double precision NOT NULL,
    plans bigint NOT NULL,
    total_plan_time double precision NOT NULL,
    wal_records bigint NOT NULL,
    wal_fpi bigint NOT NULL,
    wal_bytes numeric NOT NULL,
    jit_functions bigint NOT NULL,
    jit_generation_time double precision NOT NULL,
    jit_inlining_count bigint NOT NULL,
    jit_inlining_time double precision NOT NULL,
    jit_optimization_count bigint NOT NULL,
    jit_optimization_time double precision NOT NULL,
    jit_emission_count bigint NOT NULL,
    jit_emission_time double precision NOT NULL,
    jit_deform_count bigint NOT NULL,
    jit_deform_time double precision NOT NULL
);

CREATE UNLOGGED TABLE @extschema@.powa_user_functions_src_tmp (
    srvid integer NOT NULL,
    ts  timestamp with time zone NOT NULL,
    dbid oid NOT NULL,
    funcid oid NOT NULL,
    calls bigint NOT NULL,
    total_time double precision NOT NULL,
    self_time double precision NOT NULL
);

CREATE UNLOGGED TABLE @extschema@.powa_all_indexes_src_tmp (
    srvid integer NOT NULL,
    ts  timestamp with time zone NOT NULL,
    dbid oid NOT NULL,
    relid oid NOT NULL,
    indexrelid oid NOT NULL,
    idx_size bigint NOT NULL,
    idx_scan bigint,
    last_idx_scan timestamp with time zone,
    idx_tup_read bigint,
    idx_tup_fetch bigint,
    idx_blks_read bigint,
    idx_blks_hit bigint
);

CREATE UNLOGGED TABLE @extschema@.powa_all_tables_src_tmp (
    srvid integer NOT NULL,
    ts  timestamp with time zone NOT NULL,
    dbid oid NOT NULL,
    relid oid NOT NULL,
    tbl_size bigint NOT NULL,
    seq_scan bigint,
    last_seq_scan timestamp with time zone,
    seq_tup_read bigint,
    idx_scan bigint,
    last_idx_scan timestamp with time zone,
    idx_tup_fetch bigint,
    n_tup_ins bigint,
    n_tup_upd bigint,
    n_tup_del bigint,
    n_tup_hot_upd bigint,
    n_tup_newpage_upd bigint,
    n_liv_tup bigint,
    n_dead_tup bigint,
    n_mod_since_analyze bigint,
    n_ins_since_vacuum bigint,
    last_vacuum timestamp with time zone,
    last_autovacuum timestamp with time zone,
    last_analyze timestamp with time zone,
    last_autoanalyze timestamp with time zone,
    vacuum_count bigint,
    autovacuum_count bigint,
    analyze_count bigint,
    autoanalyze_count bigint,
    heap_blks_read bigint,
    heap_blks_hit bigint,
    idx_blks_read bigint,
    idx_blks_hit bigint,
    toast_blks_read bigint,
    toast_blks_hit bigint,
    tidx_blks_read bigint,
    tidx_blks_hit bigint
);

CREATE UNLOGGED TABLE @extschema@.powa_catalog_database_src_tmp (
    srvid integer NOT NULL,
    oid oid NOT NULL,
    datname text NOT NULL
);

CREATE UNLOGGED TABLE @extschema@.powa_catalog_role_src_tmp (
    srvid integer NOT NULL,
    oid oid NOT NULL,
    rolname text NOT NULL,
    rolsuper boolean NOT NULL,
    rolinherit boolean NOT NULL,
    rolcreaterole boolean NOT NULL,
    rolcreatedb boolean NOT NULL,
    rolcanlogin boolean NOT NULL,
    rolreplication boolean NOT NULL,
    rolbypassrls boolean NOT NULL
);

CREATE TABLE @extschema@.powa_statements_history (
    srvid integer NOT NULL,
    queryid bigint NOT NULL,
    dbid oid NOT NULL,
    toplevel boolean NOT NULL,
    userid oid NOT NULL,
    coalesce_range tstzrange NOT NULL,
    records @extschema@.powa_statements_history_record[] NOT NULL,
    mins_in_range @extschema@.powa_statements_history_record NOT NULL,
    maxs_in_range @extschema@.powa_statements_history_record NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
ALTER TABLE @extschema@.powa_statements_history ALTER COLUMN mins_in_range SET STORAGE MAIN;
ALTER TABLE @extschema@.powa_statements_history ALTER COLUMN maxs_in_range SET STORAGE MAIN;

CREATE INDEX powa_statements_history_query_ts ON @extschema@.powa_statements_history USING gist (srvid, queryid, coalesce_range);

CREATE TABLE @extschema@.powa_statements_history_db (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    coalesce_range tstzrange NOT NULL,
    records @extschema@.powa_statements_history_record[] NOT NULL,
    mins_in_range @extschema@.powa_statements_history_record NOT NULL,
    maxs_in_range @extschema@.powa_statements_history_record NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
ALTER TABLE @extschema@.powa_statements_history_db ALTER COLUMN mins_in_range SET STORAGE MAIN;
ALTER TABLE @extschema@.powa_statements_history_db ALTER COLUMN maxs_in_range SET STORAGE MAIN;

CREATE INDEX powa_statements_history_db_ts ON @extschema@.powa_statements_history_db USING gist (srvid, dbid, coalesce_range);

CREATE TABLE @extschema@.powa_statements_history_current (
    srvid integer NOT NULL,
    queryid bigint NOT NULL,
    dbid oid NOT NULL,
    toplevel boolean NOT NULL,
    userid oid NOT NULL,
    record @extschema@.powa_statements_history_record NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX ON @extschema@.powa_statements_history_current(srvid);

CREATE TABLE @extschema@.powa_statements_history_current_db (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    record @extschema@.powa_statements_history_record NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX ON @extschema@.powa_statements_history_current_db(srvid);

CREATE TABLE @extschema@.powa_user_functions_history (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    funcid oid NOT NULL,
    coalesce_range tstzrange NOT NULL,
    records @extschema@.powa_user_functions_history_record[] NOT NULL,
    mins_in_range @extschema@.powa_user_functions_history_record NOT NULL,
    maxs_in_range @extschema@.powa_user_functions_history_record NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
ALTER TABLE @extschema@.powa_user_functions_history ALTER COLUMN mins_in_range SET STORAGE MAIN;
ALTER TABLE @extschema@.powa_user_functions_history ALTER COLUMN maxs_in_range SET STORAGE MAIN;

CREATE INDEX powa_user_functions_history_funcid_ts ON @extschema@.powa_user_functions_history USING gist (srvid, funcid, coalesce_range);

CREATE TABLE @extschema@.powa_user_functions_history_db (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    coalesce_range tstzrange NOT NULL,
    records @extschema@.powa_user_functions_history_record[] NOT NULL,
    mins_in_range @extschema@.powa_user_functions_history_record NOT NULL,
    maxs_in_range @extschema@.powa_user_functions_history_record NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
ALTER TABLE @extschema@.powa_user_functions_history_db ALTER COLUMN mins_in_range SET STORAGE MAIN;
ALTER TABLE @extschema@.powa_user_functions_history_db ALTER COLUMN maxs_in_range SET STORAGE MAIN;

CREATE INDEX powa_user_functions_history_db_dbid_ts ON @extschema@.powa_user_functions_history USING gist (srvid, dbid, coalesce_range);

CREATE TABLE @extschema@.powa_user_functions_history_current (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    funcid oid NOT NULL,
    record @extschema@.powa_user_functions_history_record NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX ON @extschema@.powa_user_functions_history_current(srvid);

CREATE TABLE @extschema@.powa_user_functions_history_current_db (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    record @extschema@.powa_user_functions_history_record NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX ON @extschema@.powa_user_functions_history_current_db(srvid);

CREATE TABLE @extschema@.powa_all_indexes_history (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    relid oid NOT NULL,
    indexrelid oid NOT NULL,
    coalesce_range tstzrange NOT NULL,
    records @extschema@.powa_all_indexes_history_record[] NOT NULL,
    mins_in_range @extschema@.powa_all_indexes_history_record NOT NULL,
    maxs_in_range @extschema@.powa_all_indexes_history_record NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
ALTER TABLE @extschema@.powa_all_indexes_history ALTER COLUMN mins_in_range SET STORAGE MAIN;
ALTER TABLE @extschema@.powa_all_indexes_history ALTER COLUMN maxs_in_range SET STORAGE MAIN;

CREATE INDEX powa_all_indexes_history_relid_ts ON @extschema@.powa_all_indexes_history USING gist (srvid, relid, coalesce_range);

CREATE TABLE @extschema@.powa_all_indexes_history_db (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    coalesce_range tstzrange NOT NULL,
    records @extschema@.powa_all_indexes_history_db_record[] NOT NULL,
    mins_in_range @extschema@.powa_all_indexes_history_db_record NOT NULL,
    maxs_in_range @extschema@.powa_all_indexes_history_db_record NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
ALTER TABLE @extschema@.powa_all_indexes_history_db ALTER COLUMN mins_in_range SET STORAGE MAIN;
ALTER TABLE @extschema@.powa_all_indexes_history_db ALTER COLUMN maxs_in_range SET STORAGE MAIN;

CREATE INDEX powa_all_indexes_history_db_dbid_ts ON @extschema@.powa_all_indexes_history_db USING gist (srvid, dbid, coalesce_range);

CREATE TABLE @extschema@.powa_all_indexes_history_current (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    relid oid NOT NULL,
    indexrelid oid NOT NULL,
    record @extschema@.powa_all_indexes_history_record NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX ON @extschema@.powa_all_indexes_history_current(srvid);

CREATE TABLE @extschema@.powa_all_indexes_history_current_db (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    record @extschema@.powa_all_indexes_history_db_record NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX ON @extschema@.powa_all_indexes_history_current_db(srvid);

CREATE TABLE @extschema@.powa_all_tables_history (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    relid oid NOT NULL,
    coalesce_range tstzrange NOT NULL,
    records @extschema@.powa_all_tables_history_record[] NOT NULL,
    mins_in_range @extschema@.powa_all_tables_history_record NOT NULL,
    maxs_in_range @extschema@.powa_all_tables_history_record NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
ALTER TABLE @extschema@.powa_all_tables_history ALTER COLUMN mins_in_range SET STORAGE MAIN;
ALTER TABLE @extschema@.powa_all_tables_history ALTER COLUMN maxs_in_range SET STORAGE MAIN;

CREATE INDEX powa_all_tables_history_relid_ts ON @extschema@.powa_all_tables_history USING gist (srvid, relid, coalesce_range);

CREATE TABLE @extschema@.powa_all_tables_history_db (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    coalesce_range tstzrange NOT NULL,
    records @extschema@.powa_all_tables_history_db_record[] NOT NULL,
    mins_in_range @extschema@.powa_all_tables_history_db_record NOT NULL,
    maxs_in_range @extschema@.powa_all_tables_history_db_record NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
ALTER TABLE @extschema@.powa_all_tables_history_db ALTER COLUMN mins_in_range SET STORAGE MAIN;
ALTER TABLE @extschema@.powa_all_tables_history_db ALTER COLUMN maxs_in_range SET STORAGE MAIN;

CREATE INDEX powa_all_tables_history_db_dbid_ts ON @extschema@.powa_all_tables_history_db USING gist (srvid, dbid, coalesce_range);

CREATE TABLE @extschema@.powa_all_tables_history_current (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    relid oid NOT NULL,
    record @extschema@.powa_all_tables_history_record NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX ON @extschema@.powa_all_tables_history_current(srvid);

CREATE TABLE @extschema@.powa_all_tables_history_current_db (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    record @extschema@.powa_all_tables_history_db_record NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX ON @extschema@.powa_all_tables_history_current_db(srvid);

-- Register the given module if needed.
CREATE FUNCTION @extschema@.powa_activate_module(_srvid int, _module text) RETURNS boolean
AS $_$
DECLARE
    v_res bool;
BEGIN
    IF (_srvid IS NULL) THEN
        RAISE EXCEPTION 'powa_activate_module: no server id provided';
    END IF;

    IF (_module IS NULL) THEN
        RAISE EXCEPTION 'powa_activate_module: no module provided';
    END IF;

    -- Check that the module is known.
    SELECT COUNT(*) = 1 INTO v_res
    FROM @extschema@.powa_modules
    WHERE module = _module;

    IF (NOT v_res) THEN
        RAISE EXCEPTION 'Module "%" is not known', _module;
    END IF;

    -- The record may already be present, but the enabled flag could be off.
    -- If so simply enable it.  Otherwise, add the needed record.
    SELECT COUNT(*) > 0 INTO v_res
    FROM @extschema@.powa_module_config
    WHERE module = _module
    AND srvid = _srvid;

    IF (v_res) THEN
        UPDATE @extschema@.powa_module_config
        SET enabled = true
        WHERE enabled = false
        AND srvid = _srvid
        AND module = _module;
    ELSE
        INSERT INTO @extschema@.powa_module_config
            (srvid, module)
        VALUES
            (_srvid, _module);
    END IF;

    RETURN true;
END;
$_$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_activate_module */

-- Deactivate a module: leave the record in powa_module_config but
-- remove the enabled flag.
CREATE FUNCTION @extschema@.powa_deactivate_module(_srvid int, _module text) RETURNS boolean
AS $_$
DECLARE
    v_res bool;
BEGIN
    IF (_srvid IS NULL) THEN
        RAISE EXCEPTION 'powa_deactivate_module: no server id provided';
    END IF;

    IF (_module IS NULL) THEN
        RAISE EXCEPTION 'powa_deactivate_module: no module provided';
    END IF;

    -- Check that the module is known.
    SELECT COUNT(*) = 1 INTO v_res
    FROM @extschema@.powa_modules
    WHERE module = _module;

    IF (NOT v_res) THEN
        RAISE EXCEPTION 'Module "%" is not known', _module;
    END IF;

    UPDATE @extschema@.powa_module_config
    SET enabled = false
    WHERE enabled = true
    AND srvid = _srvid
    AND module = _module;

    RETURN true;
END;
$_$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_deactivate_module */

-- Register the given db module if needed.
CREATE FUNCTION @extschema@.powa_activate_db_module(_srvid int, _db_module text,
                                                    databases text[] DEFAULT NULL)
RETURNS boolean
AS $_$
DECLARE
    v_res bool;
    v_enabled bool;
    v_dbnames text[];
BEGIN
    IF (_srvid IS NULL) THEN
        RAISE EXCEPTION 'powa_activate_db_module: no server id provided';
    END IF;

    IF (_db_module IS NULL) THEN
        RAISE EXCEPTION 'powa_activate_db_module: no module provided';
    END IF;

    -- Check that the module is known.
    SELECT COUNT(*) = 1 INTO v_res
    FROM @extschema@.powa_db_modules
    WHERE db_module = _db_module;

    IF (NOT v_res) THEN
        RAISE EXCEPTION 'Database module "%" is not known', _db_module;
    END IF;

    -- We need to check if there's an existing row or not, and if yes what is
    -- the current dbnames value.  As dbnames is NULLable, also retrieve the
    -- enabled field: we can use it to know if the row already exist and also
    -- to avoid a useless UPDATE.
    SELECT enabled, dbnames INTO v_enabled, v_dbnames
    FROM @extschema@.powa_db_module_config
    WHERE db_module = _db_module
    AND srvid = _srvid;

    IF (v_enabled IS NOT NULL) THEN
        -- There's an existing row.  Update the dbnames and enable the db
        -- module if needed.  Note that we don't try hard to detect whether the
        -- dbnames are semantically equivalent.
        IF (v_enabled AND v_dbnames IS NOT DISTINCT FROM databases) THEN
            -- existing info already matches, bail out
            RETURN true;
        END IF;
        UPDATE @extschema@.powa_db_module_config
        SET enabled = true, dbnames = databases
        WHERE srvid = _srvid
        AND db_module = _db_module;
    ELSE
        -- Just insert the wanted informations
        INSERT INTO @extschema@.powa_db_module_config
            (srvid, db_module, dbnames)
        VALUES
            (_srvid, _db_module, databases);
    END IF;

    RETURN true;
END;
$_$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_activate_db_module */

-- Deactivate a db module: leave the record in powa_db_module_config but
-- remove the enabled flag.
CREATE FUNCTION @extschema@.powa_deactivate_db_module(_srvid int,
                                                      _db_module text,
                                                      databases text[] DEFAULT NULL)
RETURNS boolean
AS $_$
DECLARE
    v_res bool;
    v_enabled bool;
    v_dbnames text[];
    v_new_dbnames text[];
BEGIN
    IF (_srvid IS NULL) THEN
        RAISE EXCEPTION 'powa_deactivate_db_module: no server id provided';
    END IF;

    IF (_db_module IS NULL) THEN
        RAISE EXCEPTION 'powa_deactivate_db_module: no module provided';
    END IF;

    -- Check that the module is known.
    SELECT COUNT(*) = 1 INTO v_res
    FROM @extschema@.powa_db_modules
    WHERE db_module = _db_module;

    IF (NOT v_res) THEN
        RAISE EXCEPTION 'db module "%" is not known', _db_module;
    END IF;

    -- We need to check if there's an existing row or not, and if yes what is
    -- the current dbnames value.  As dbnames is NULLable, also retrieve the
    -- enabled field: we can use it to know if the row already exist and also
    -- to avoid a useless UPDATE.
    SELECT enabled, dbnames INTO v_enabled, v_dbnames
    FROM @extschema@.powa_db_module_config
    WHERE db_module = _db_module
    AND srvid = _srvid;

    -- Deactivating a non configured db module is a not allowed
    IF (v_enabled IS NULL) THEN
        RAISE EXCEPTION 'db module "%" is not configured', _db_module;
    END IF;

    -- Deactivating a deactivated db module is a noop, as in that case there
    -- shouldn't dbnames
    IF (v_enabled IS FALSE) THEN
        ASSERT v_dbnames IS NULL, 'A deactivated db module shouldn''t contain db names';
        RETURN true;
    END IF;

    -- We don't support deactivating only some databases if this is an "all-db"
    -- record
    IF (databases IS NOT NULL AND v_dbnames IS NULL) THEN
        RAISE EXCEPTION 'cannot deactivate a db module for a specific database if no specific database is configured';
    END IF;

    -- If users asked to deactivate only for some databases, error out if any
    -- of the wanted datbase isn't already activated
    IF (databases IS NOT NULL AND NOT databases <@ v_dbnames) THEN
        RAISE EXCEPTION 'cannot deactivate a db module for a specific database if not already activated on that database';
    END IF;

    -- If there's no record dbname, simply deactivate the module
    IF (databases IS NULL) THEN
        -- existing info already matches, bail out
        IF (NOT v_enabled AND v_dbnames IS NULL) THEN
            RETURN true;
        END IF;
        UPDATE @extschema@.powa_db_module_config
        SET enabled = false, dbnames = NULL
        WHERE srvid = _srvid
        AND db_module = _db_module;
    ELSIF (v_dbnames IS NULL) THEN
        -- stored dbnames isn't NULL but user didn't provide any.  Reset all
        UPDATE @extschema@.powa_db_module_config
        SET enabled = false, dbnames = NULL
        WHERE srvid = _srvid
        AND db_module = _db_module;
    ELSE
        -- There are stored dbnames and users provided some.  Keep the module
        -- activated and simply remove the specified dbnames
        ASSERT v_enabled IS TRUE, 'Module should be enabled';
        SELECT array_agg(dbname ORDER BY dbname)
        FROM (
            SELECT unnest(v_dbnames)
            EXCEPT
            SELECT unnest(databases)
        ) s(dbname) INTO v_new_dbnames;

        IF (coalesce(cardinality(v_new_dbnames), 0) = 0) THEN
            -- If everything was removed, clear all dbnames and disable the db
            -- module
            UPDATE @extschema@.powa_db_module_config
            SET enabled = false, dbnames = NULL
            WHERE srvid = _srvid
            AND db_module = _db_module;
        ELSE
            ASSERT v_enabled IS TRUE, 'Module should be enabled';
            -- otherwise just save the new data
            UPDATE @extschema@.powa_db_module_config
            SET dbnames = v_new_dbnames
            WHERE srvid = _srvid
            AND db_module = _db_module;
        END IF;
    END IF;

    RETURN true;
END;
$_$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_deactivate_db_module */

-- Register the given extension if needed, and set the enabled flag to on.
CREATE FUNCTION @extschema@.powa_activate_extension(_srvid integer, _extname text) RETURNS boolean
AS $_$
DECLARE
    v_res boolean;
BEGIN
    IF (_srvid IS NULL) THEN
        RAISE EXCEPTION 'powa_activate_extension: no server id provided';
    END IF;

    IF (_extname IS NULL) THEN
        RAISE EXCEPTION 'powa_activate_extension: no extension provided';
    END IF;

    -- Check that the extension is known.
    SELECT COUNT(*) = 1 INTO v_res
    FROM @extschema@.powa_extensions
    WHERE extname = _extname;

    IF (NOT v_res) THEN
        RAISE WARNING 'powa_activate_extension "%" is not known', _extname;
        RETURN false;
    END IF;

    -- For local server, check that the extension has been created before
    -- blindly activating it.  It allows us to simply try to activate all known
    -- extensions for the local server by default.
    IF (_srvid = 0) THEN
        SELECT COUNT(*) = 1 INTO v_res
        FROM pg_catalog.pg_extension
        WHERE extname = _extname;

        IF (NOT v_res) THEN
            RAISE WARNING 'Extension % is not installed locally, ignoring',
                          _extname;
            RETURN false;
        END IF;
    END IF;

    -- Activating the "powa" extension is an alias for activating all the
    -- underlying (default) modules.  We don't activate db modules for the
    -- local server though, as those are only process by powa-collector, which
    -- ignores the local server.
    IF (_extname = 'powa') THEN
        SELECT bool_and(v)
        FROM (
            SELECT @extschema@.powa_activate_module(_srvid, module)
            FROM @extschema@.powa_modules
            UNION ALL
            SELECT @extschema@.powa_activate_db_module(_srvid, db_module)
            FROM @extschema@.powa_db_modules
            WHERE _srvid != 0
            AND NOT added_manually
        ) s(v) INTO v_res;

        RETURN v_res;
    END IF;

    -- The record may already be present, but the enabled flag could be off.
    -- If so simply enable it.  Otherwise, add the needed record.
    SELECT COUNT(*) > 0 INTO v_res
    FROM @extschema@.powa_extension_config
    WHERE extname = _extname
    AND srvid = _srvid;

    IF (v_res) THEN
        UPDATE @extschema@.powa_extension_config
        SET enabled = true
        WHERE enabled = false
        AND srvid = _srvid
        AND extname = _extname;
    ELSE
        INSERT INTO @extschema@.powa_extension_config
            (srvid, extname, added_manually)
        VALUES
            (_srvid, _extname, (_srvid != 0));
    END IF;

    RETURN true;
END;
$_$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_activate_extension */

-- Deactivate an extension: leave the record in powa_extension_config but
-- remove the enabled flag.
CREATE FUNCTION @extschema@.powa_deactivate_extension(_srvid integer, _extname text) RETURNS boolean
AS $_$
DECLARE
    v_res bool;
BEGIN
    IF (_srvid IS NULL) THEN
        RAISE EXCEPTION 'powa_activate_extension: no server id provided';
    END IF;

    IF (_extname IS NULL) THEN
        RAISE EXCEPTION 'powa_activate_extension: no extension provided';
    END IF;

    -- Deactivating extension "powa" is an alias for deactivating all the
    -- underlying modules.
    IF (_extname = 'powa') THEN
        SELECT bool_and(@extschema@.powa_deactivate_module(_srvid, module))
            INTO v_res
        FROM @extschema@.powa_modules;

        RETURN v_res;
    ELSE
        UPDATE @extschema@.powa_extension_config
        SET enabled = false
        WHERE extname = _extname
        AND srvid = _srvid;
    END IF;

    return true;
END;
$_$ LANGUAGE plpgsql; /* end of powa_deactivate_extension */

CREATE FUNCTION @extschema@.powa_register_server(hostname text,
    port integer DEFAULT 5432,
    alias text DEFAULT NULL,
    username text DEFAULT 'powa',
    password text DEFAULT NULL,
    dbname text DEFAULT 'powa',
    frequency integer DEFAULT 300,
    powa_coalesce integer default 100,
    retention interval DEFAULT '1 day'::interval,
    allow_ui_connection boolean DEFAULT true,
    extensions text[] DEFAULT NULL)
RETURNS boolean AS $_$
DECLARE
    v_ok boolean;
    v_srvid integer;
    v_extname text;
BEGIN
    -- sanity checks
    SELECT coalesce(port, 5432), coalesce(username, 'powa'),
        coalesce(dbname, 'powa'), coalesce(frequency, 300),
        coalesce(powa_coalesce, 100), coalesce(retention, '1 day')::interval,
        coalesce(allow_ui_connection, true)
    INTO port, username, dbname, frequency, powa_coalesce, retention, allow_ui_connection;

    INSERT INTO @extschema@.powa_servers
        (alias, hostname, port, username, password, dbname, frequency, powa_coalesce, retention, allow_ui_connection)
    VALUES
        (alias, hostname, port, username, password, dbname, frequency, powa_coalesce, retention, allow_ui_connection)
    RETURNING id INTO v_srvid;

    INSERT INTO @extschema@.powa_snapshot_metas(srvid) VALUES (v_srvid);

    -- always register pgss, as it's mandatory
    SELECT @extschema@.powa_activate_extension(v_srvid, 'pg_stat_statements') INTO v_ok;
    IF (NOT v_ok) THEN
        RAISE EXCEPTION 'Could not activate pg_stat_statements';
    END IF;
    -- and also the powa, which is an alias for activating all its modules
    SELECT @extschema@.powa_activate_extension(v_srvid, 'powa') INTO v_ok;
    IF (NOT v_ok) THEN
        RAISE EXCEPTION 'Could not activate powa modules';
    END IF;

    -- If no extra extensions were asked, we're done
    IF extensions IS NULL THEN
        RETURN true;
    END IF;

    FOREACH v_extname IN ARRAY extensions
    LOOP
        -- don't process pg_stat_statements or powa extensions as those are
        -- already forced
        CONTINUE WHEN v_extname IN ('pg_stat_statements', 'powa');

        SELECT @extschema@.powa_activate_extension(v_srvid, v_extname) INTO v_ok;

        IF (v_ok IS DISTINCT FROM true) THEN
            RAISE WARNING 'Could not activate extension % on server %:%',
                v_extname, hostname, port;
        END IF;
    END LOOP;
    RETURN true;
END;
$_$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_register_server */

CREATE FUNCTION @extschema@.powa_configure_server(_srvid integer, data json) RETURNS boolean
AS $_$
DECLARE
    v_rowcount bigint;
    k text;
    v text;
    v_query text = '';
BEGIN
    IF (_srvid = 0) THEN
        RAISE EXCEPTION 'Local server cannot be configured';
    END IF;

    IF (data IS NULL) THEN
        RAISE EXCEPTION 'No data provided';
    END IF;

    FOR k, v IN SELECT * FROM json_each_text(data) LOOP
        IF (k = 'id') THEN
            RAISE EXCEPTION 'Updating server id is not allowed';
        END IF;

        IF (k NOT IN ('hostname', 'alias', 'port', 'username', 'password',
            'dbname', 'frequency', 'retention', 'allow_ui_connection')
        ) THEN
            RAISE EXCEPTION 'Unknown field: %', k;
        END IF;

        IF (v_query != '') THEN
            v_query := v_query || ', ';
        END IF;
        v_query := v_query || format('%I = %L', k, v);
    END LOOP;

    v_query := 'UPDATE @extschema@.powa_servers SET '
        || v_query
        || format(' WHERE id = %s', _srvid);

    EXECUTE v_query;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;

    RETURN v_rowcount = 1;
END;
$_$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* powa_config_server */

CREATE FUNCTION @extschema@.powa_deactivate_server(_srvid integer) RETURNS boolean
AS $_$
DECLARE
    v_rowcount bigint;
BEGIN
    IF (_srvid = 0) THEN
        RAISE EXCEPTION 'Local server cannot be updated';
    END IF;

    UPDATE @extschema@.powa_servers SET frequency = -1 WHERE id = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;

    RETURN v_rowcount = 1;
END;
$_$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* powa_deactivate_server */

CREATE FUNCTION @extschema@.powa_delete_and_purge_server(_srvid integer) RETURNS boolean
AS $_$
DECLARE
    v_rowcount bigint;
    v_extnsp text;
BEGIN
    IF (_srvid = 0) THEN
        RAISE EXCEPTION 'Local server cannot be deleted';
    END IF;

    DELETE FROM @extschema@.powa_servers WHERE id = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;

    -- pg_track_settings is an autonomous extension, so it doesn't have a FK to
    -- powa_servers.  It therefore needs to be processed manually
    SELECT COUNT(*), nspname
        FROM pg_extension e
        LEFT JOIN pg_namespace n ON n.oid = e.extnamespace
        WHERE extname = 'pg_track_settings'
        GROUP BY nspname
        INTO v_rowcount, v_extnsp;
    IF (v_rowcount = 1) THEN
        EXECUTE format('DELETE FROM %I.pg_track_settings_list WHERE srvid = %s',
            v_extnsp,
            _srvid);
        EXECUTE format('DELETE FROM %I.pg_track_settings_history WHERE srvid = %s',
            v_extnsp,
            _srvid);
        EXECUTE format('DELETE FROM %I.pg_track_db_role_settings_list WHERE srvid = %s',
            v_extnsp,
            _srvid);
        EXECUTE format('DELETE FROM %I.pg_track_db_role_settings_history WHERE srvid = %s',
            v_extnsp,
            _srvid);
        EXECUTE format('DELETE FROM %I.pg_reboot WHERE srvid = %s',
            v_extnsp,
            _srvid);
    END IF;

    RETURN v_rowcount = 1;
END;
$_$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* powa_delete_and_purge_server */

DO $anon$
BEGIN
    IF current_setting('server_version_num')::int < 90600 THEN
        CREATE FUNCTION @extschema@.powa_get_guc (guc text, def text DEFAULT NULL) RETURNS text
        LANGUAGE plpgsql
        AS $_$
        DECLARE
            v_val text;
        BEGIN
            BEGIN
                SELECT current_setting(guc) INTO v_val;
            EXCEPTION WHEN OTHERS THEN
                v_val = def;
            END;

            RETURN v_val;
        END;
        $_$
        SET search_path = pg_catalog;
    ELSE
        CREATE FUNCTION @extschema@.powa_get_guc (guc text, def text DEFAULT NULL) RETURNS text
        LANGUAGE plpgsql
        AS $_$
        BEGIN
            RETURN COALESCE(current_setting(guc, true), def);
        END;
        $_$
        SET search_path = pg_catalog;
    END IF;
END;
$anon$;

CREATE FUNCTION @extschema@.powa_log (msg text) RETURNS void
LANGUAGE plpgsql
AS $_$
BEGIN
    IF @extschema@.powa_get_guc('powa.debug', 'false')::bool THEN
        RAISE WARNING '%', msg;
    ELSE
        RAISE DEBUG '%', msg;
    END IF;
END;
$_$
SET search_path = pg_catalog;

CREATE FUNCTION @extschema@.powa_get_server_retention(_srvid integer)
RETURNS interval AS $_$
DECLARE
    v_ret interval = NULL;
BEGIN
    IF (_srvid = 0) THEN
        v_ret := current_setting('powa.retention')::interval;
    ELSE
        SELECT retention INTO v_ret
        FROM @extschema@.powa_servers
        WHERE id = _srvid;
    END IF;

    IF (v_ret IS NULL) THEN
        RAISE EXCEPTION 'Not retention found for server %', _srvid;
    END IF;

    RETURN v_ret;
END;
$_$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_get_server_retention */

/* pg_stat_kcache integration - part 1 */

CREATE UNLOGGED TABLE @extschema@.powa_kcache_src_tmp (
    srvid            integer NOT NULL,
    ts               timestamp with time zone NOT NULL,
    queryid          bigint NOT NULL,
    top              bool NOT NULL,
    userid           oid NOT NULL,
    dbid             oid NOT NULL,
    plan_reads       bigint,
    plan_writes      bigint,
    plan_user_time   double precision,
    plan_system_time double precision,
    plan_minflts     bigint,
    plan_majflts     bigint,
    plan_nswaps      bigint,
    plan_msgsnds     bigint,
    plan_msgrcvs     bigint,
    plan_nsignals    bigint,
    plan_nvcsws      bigint,
    plan_nivcsws     bigint,
    exec_reads       bigint,
    exec_writes      bigint,
    exec_user_time   double precision NOT NULL,
    exec_system_time double precision NOT NULL,
    exec_minflts     bigint,
    exec_majflts     bigint,
    exec_nswaps      bigint,
    exec_msgsnds     bigint,
    exec_msgrcvs     bigint,
    exec_nsignals    bigint,
    exec_nvcsws      bigint,
    exec_nivcsws     bigint
);

CREATE TABLE @extschema@.powa_kcache_metrics (
    srvid integer NOT NULL,
    coalesce_range tstzrange NOT NULL,
    queryid bigint NOT NULL,
    dbid oid NOT NULL,
    userid oid NOT NULL,
    metrics @extschema@.powa_kcache_history_record[] NOT NULL,
    mins_in_range @extschema@.powa_kcache_history_record NOT NULL,
    maxs_in_range @extschema@.powa_kcache_history_record NOT NULL,
    top boolean NOT NULL,
    PRIMARY KEY (srvid, coalesce_range, queryid, dbid, userid, top),
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
ALTER TABLE @extschema@.powa_kcache_metrics ALTER COLUMN mins_in_range SET STORAGE MAIN;
ALTER TABLE @extschema@.powa_kcache_metrics ALTER COLUMN maxs_in_range SET STORAGE MAIN;

CREATE INDEX ON @extschema@.powa_kcache_metrics (srvid, queryid);

CREATE TABLE @extschema@.powa_kcache_metrics_db (
    srvid integer NOT NULL,
    coalesce_range tstzrange NOT NULL,
    dbid oid NOT NULL,
    metrics @extschema@.powa_kcache_history_record[] NOT NULL,
    mins_in_range @extschema@.powa_kcache_history_record NOT NULL,
    maxs_in_range @extschema@.powa_kcache_history_record NOT NULL,
    top boolean NOT NULL,
    PRIMARY KEY (srvid, coalesce_range, dbid, top),
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
ALTER TABLE @extschema@.powa_kcache_metrics_db ALTER COLUMN mins_in_range SET STORAGE MAIN;
ALTER TABLE @extschema@.powa_kcache_metrics_db ALTER COLUMN maxs_in_range SET STORAGE MAIN;

CREATE TABLE @extschema@.powa_kcache_metrics_current ( srvid integer NOT NULL,
    queryid bigint NOT NULL,
    dbid oid NOT NULL,
    userid oid NOT NULL,
    metrics @extschema@.powa_kcache_history_record NULL NULL,
    top boolean NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX ON @extschema@.powa_kcache_metrics_current(srvid);

CREATE TABLE @extschema@.powa_kcache_metrics_current_db (
    srvid integer NOT NULL,
    dbid oid NOT NULL,
    metrics @extschema@.powa_kcache_history_record NULL NULL,
    top boolean NOT NULL,
    FOREIGN KEY (srvid) REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX ON @extschema@.powa_kcache_metrics_current_db(srvid);

/* end of pg_stat_kcache integration - part 1 */

/* pg_qualstats integration - part 1 */

CREATE TYPE @extschema@.qual_type AS (
    relid oid,
    attnum integer,
    opno oid,
    eval_type "char"
);

CREATE TYPE @extschema@.qual_values AS (
    constants text[],
    occurences bigint,
    execution_count bigint,
    nbfiltered bigint,
    mean_err_estimate_ratio double precision,
    mean_err_estimate_num double precision
);

CREATE UNLOGGED TABLE @extschema@.powa_qualstats_src_tmp (
    srvid integer NOT NULL,
    ts timestamp with time zone NOT NULL,
    uniquequalnodeid bigint NOT NULL,
    dbid oid NOT NULL,
    userid oid NOT NULL,
    qualnodeid bigint NOT NULL,
    occurences bigint NOT NULL,
    execution_count bigint NOT NULL,
    nbfiltered bigint NOT NULL,
    mean_err_estimate_ratio double precision,
    mean_err_estimate_num double precision,
    queryid bigint NOT NULL,
    constvalues varchar[] NOT NULL,
    quals @extschema@.qual_type[] NOT NULL
);

CREATE TABLE @extschema@.powa_qualstats_quals (
    srvid integer NOT NULL,
    qualid bigint,
    queryid bigint,
    dbid oid,
    userid oid,
    quals @extschema@.qual_type[],
    PRIMARY KEY (srvid, qualid, queryid, dbid, userid),
    FOREIGN KEY (srvid, queryid, dbid, userid) REFERENCES @extschema@.powa_statements(srvid, queryid, dbid, userid)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX ON @extschema@.powa_qualstats_quals(srvid, queryid);

CREATE TABLE @extschema@.powa_qualstats_quals_history (
    srvid integer NOT NULL,
    qualid bigint,
    queryid bigint,
    dbid oid,
    userid oid,
    coalesce_range tstzrange,
    records @extschema@.powa_qualstats_history_record[],
    mins_in_range @extschema@.powa_qualstats_history_record,
    maxs_in_range @extschema@.powa_qualstats_history_record,
    FOREIGN KEY (srvid, qualid, queryid, dbid, userid) REFERENCES @extschema@.powa_qualstats_quals (srvid, qualid, queryid, dbid, userid) MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
ALTER TABLE @extschema@.powa_qualstats_quals_history ALTER COLUMN mins_in_range SET STORAGE MAIN;
ALTER TABLE @extschema@.powa_qualstats_quals_history ALTER COLUMN maxs_in_range SET STORAGE MAIN;

CREATE INDEX powa_qualstats_quals_history_query_ts ON @extschema@.powa_qualstats_quals_history USING gist (srvid, queryid, coalesce_range);

CREATE TABLE @extschema@.powa_qualstats_quals_history_current (
    srvid integer NOT NULL,
    qualid bigint,
    queryid bigint,
    dbid oid,
    userid oid,
    ts timestamptz,
    occurences bigint,
    execution_count   bigint,
    nbfiltered bigint,
    mean_err_estimate_ratio double precision,
    mean_err_estimate_num double precision,
    FOREIGN KEY (srvid, qualid, queryid, dbid, userid) REFERENCES @extschema@.powa_qualstats_quals(srvid, qualid, queryid, dbid, userid)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX ON @extschema@.powa_qualstats_quals_history_current(srvid);

CREATE TABLE @extschema@.powa_qualstats_constvalues_history (
    srvid integer NOT NULL,
    qualid bigint,
    queryid bigint,
    dbid oid,
    userid oid,
    coalesce_range tstzrange,
    most_used @extschema@.qual_values[],
    most_filtering @extschema@.qual_values[],
    least_filtering @extschema@.qual_values[],
    most_executed @extschema@.qual_values[],
    most_errestim_ratio @extschema@.qual_values[],
    most_errestim_num @extschema@.qual_values[],
    FOREIGN KEY (srvid, qualid, queryid, dbid, userid) REFERENCES @extschema@.powa_qualstats_quals (srvid, qualid, queryid, dbid, userid) MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX ON @extschema@.powa_qualstats_constvalues_history USING gist (srvid, queryid, qualid, coalesce_range);
CREATE INDEX ON @extschema@.powa_qualstats_constvalues_history (srvid, qualid, queryid);

CREATE TABLE @extschema@.powa_qualstats_constvalues_history_current (
    srvid integer NOT NULL,
    qualid bigint,
    queryid bigint,
    dbid oid,
    userid oid,
    ts timestamptz,
    constvalues text[],
    occurences bigint,
    execution_count bigint,
    nbfiltered bigint,
    mean_err_estimate_ratio double precision,
    mean_err_estimate_num double precision,
    FOREIGN KEY (srvid, qualid, queryid, dbid, userid) REFERENCES @extschema@.powa_qualstats_quals (srvid, qualid, queryid, dbid, userid) MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX ON @extschema@.powa_qualstats_constvalues_history_current(srvid);


/* end of pg_qualstats_integration - part 1 */

/* pg_wait_sampling integration - part 1 */

CREATE UNLOGGED TABLE @extschema@.powa_wait_sampling_src_tmp (
    srvid integer NOT NULL,
    ts timestamp with time zone NOT NULL,
    dbid oid NOT NULL,
    event_type text NOT NULL,
    event text NOT NULL,
    queryid bigint NOT NULL,
    count numeric NOT NULL
);

CREATE TABLE @extschema@.powa_wait_sampling_history (
    srvid integer NOT NULL REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE,
    coalesce_range tstzrange NOT NULL,
    queryid bigint NOT NULL,
    dbid oid NOT NULL,
    event_type text NOT NULL,
    event text NOT NULL,
    records @extschema@.powa_wait_sampling_history_record[] NOT NULL,
    mins_in_range @extschema@.powa_wait_sampling_history_record NOT NULL,
    maxs_in_range @extschema@.powa_wait_sampling_history_record NOT NULL,
    PRIMARY KEY (srvid, coalesce_range, queryid, dbid, event_type, event)
);
ALTER TABLE @extschema@.powa_wait_sampling_history ALTER COLUMN mins_in_range SET STORAGE MAIN;
ALTER TABLE @extschema@.powa_wait_sampling_history ALTER COLUMN maxs_in_range SET STORAGE MAIN;

CREATE INDEX powa_wait_sampling_history_query_ts ON @extschema@.powa_wait_sampling_history USING gist (srvid, queryid, coalesce_range);

CREATE TABLE @extschema@.powa_wait_sampling_history_db (
    srvid integer NOT NULL REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE,
    coalesce_range tstzrange NOT NULL,
    dbid oid NOT NULL,
    event_type text NOT NULL,
    event text NOT NULL,
    records @extschema@.powa_wait_sampling_history_record[] NOT NULL,
    mins_in_range @extschema@.powa_wait_sampling_history_record NOT NULL,
    maxs_in_range @extschema@.powa_wait_sampling_history_record NOT NULL,
    PRIMARY KEY (srvid, coalesce_range, dbid, event_type, event)
);
ALTER TABLE @extschema@.powa_wait_sampling_history_db ALTER COLUMN mins_in_range SET STORAGE MAIN;
ALTER TABLE @extschema@.powa_wait_sampling_history_db ALTER COLUMN maxs_in_range SET STORAGE MAIN;

CREATE INDEX powa_wait_sampling_history_db_ts ON @extschema@.powa_wait_sampling_history_db USING gist (srvid, dbid, coalesce_range);

CREATE TABLE @extschema@.powa_wait_sampling_history_current (
    srvid integer NOT NULL REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE,
    queryid bigint NOT NULL,
    dbid oid NOT NULL,
    event_type text NOT NULL,
    event text NOT NULL,
    record @extschema@.powa_wait_sampling_history_record NOT NULL
);
CREATE INDEX ON @extschema@.powa_wait_sampling_history_current(srvid);

CREATE TABLE @extschema@.powa_wait_sampling_history_current_db (
    srvid integer NOT NULL REFERENCES @extschema@.powa_servers(id)
      MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE,
    dbid oid NOT NULL,
    event_type text NOT NULL,
    event text NOT NULL,
    record @extschema@.powa_wait_sampling_history_record NOT NULL
);
CREATE INDEX ON @extschema@.powa_wait_sampling_history_current_db(srvid);

/* end of pg_wait_sampling integration - part 1 */

-- Mark all of powa's tables as "to be dumped"
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_servers','WHERE id > 0');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_snapshot_metas','WHERE srvid > 0');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_databases','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_statements','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_statements_history','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_statements_history_db','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_statements_history_current','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_statements_history_current_db','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_user_functions_history','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_user_functions_history_db','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_user_functions_history_current','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_user_functions_history_current_db','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_all_indexes_history','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_all_indexes_history_db','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_all_indexes_history_current','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_all_indexes_history_current_db','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_all_tables_history','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_all_tables_history_db','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_all_tables_history_current','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_all_tables_history_current_db','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_extensions','WHERE added_manually');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_extension_functions','WHERE added_manually');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_extension_config','WHERE added_manually');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_module_functions','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_module_config','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_catalog_databases','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_catalog_roles','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_catalog_class','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_catalog_attribute','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_catalog_namespace','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_catalog_type','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_catalog_collation','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_catalog_proc','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_catalog_language','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_db_modules','WHERE added_manually');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_db_module_functions','WHERE added_manually');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_db_module_config', '');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_db_module_src_queries','WHERE added_manually');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_kcache_metrics','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_kcache_metrics_db','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_kcache_metrics_current','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_kcache_metrics_current_db','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_qualstats_quals','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_qualstats_quals_history','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_qualstats_quals_history_current','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_qualstats_constvalues_history','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_qualstats_constvalues_history_current','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_wait_sampling_history','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_wait_sampling_history_db','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_wait_sampling_history_current','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_wait_sampling_history_current_db','');

-- automatically configure powa for local snapshot if supported extension are
-- created locally
CREATE OR REPLACE FUNCTION @extschema@.powa_check_created_extensions()
RETURNS event_trigger
LANGUAGE plpgsql
AS $_$
DECLARE
    v_extname text;
    v_res bool;
BEGIN
    SELECT extname INTO v_extname
    FROM pg_event_trigger_ddl_commands() d
    JOIN pg_extension e ON d.classid = 'pg_extension'::regclass
        AND d.objid = e.oid
    JOIN @extschema@.powa_extensions p USING (extname)
    WHERE d.object_type = 'extension';

    -- Bail out if this isn't a known extension
    IF (v_extname IS NULL) THEN
        RETURN;
    END IF;

    SELECT @extschema@.powa_activate_extension(0, v_extname) INTO v_res;

    IF (NOT v_res) THEN
        RAISE WARNING 'Could not automatically activate extension "%"', v_extname;
    END IF;
END;
$_$
SET search_path = pg_catalog; /* end of powa_check_created_extensions */

CREATE EVENT TRIGGER powa_check_created_extensions
    ON ddl_command_end
    WHEN tag IN ('CREATE EXTENSION')
    EXECUTE PROCEDURE @extschema@.powa_check_created_extensions() ;

-- automatically remove extensions from local snapshot if supported extension
-- is removed locally
CREATE OR REPLACE FUNCTION @extschema@.powa_check_dropped_extensions()
RETURNS event_trigger
LANGUAGE plpgsql
AS $_$
DECLARE
    r         record;
    v_state   text;
    v_msg     text;
    v_detail  text;
    v_hint    text;
    v_context text;
BEGIN
    -- We unregister extensions regardless the "enabled" field
    WITH src AS (
        SELECT object_name
        FROM pg_event_trigger_dropped_objects() d
        WHERE d.object_type = 'extension'
    )
    SELECT CASE external
        WHEN true THEN quote_ident(nsp.nspname)
        ELSE '@extschema@'
        END AS schema, function_name AS funcname INTO r
    FROM @extschema@.powa_extensions AS pe
    JOIN src ON pe.module = src.object_name
    LEFT JOIN pg_extension AS ext ON ext.extname = pe.extname
    LEFT JOIN pg_namespace AS nsp ON nsp.oid = ext.extnamespace
    WHERE operation = 'unregister'
    ORDER BY module;

    IF ( r.funcname IS NOT NULL ) THEN
        BEGIN
            PERFORM @extschema@.powa_log(format('running %s.%I',
                    r.schema, r.funcname));
            EXECUTE format('SELECT %s.%I(0)', r.schema, r.funcname);
        EXCEPTION
          WHEN OTHERS THEN
            GET STACKED DIAGNOSTICS
                v_state   = RETURNED_SQLSTATE,
                v_msg     = MESSAGE_TEXT,
                v_detail  = PG_EXCEPTION_DETAIL,
                v_hint    = PG_EXCEPTION_HINT,
                v_context = PG_EXCEPTION_CONTEXT;
            RAISE WARNING '@extschema@.powa_check_dropped_extensions(): function %.% failed:
                state  : %
                message: %
                detail : %
                hint   : %
                context: %', r.schema, quote_ident(r.funcname), v_state, v_msg,
                             v_detail, v_hint, v_context;
        END;
    END IF;
END;
$_$
SET search_path = pg_catalog; /* end of powa_check_dropped_extensions */

CREATE EVENT TRIGGER powa_check_dropped_extensions
    ON sql_drop
    WHEN tag IN ('DROP EXTENSION')
    EXECUTE PROCEDURE @extschema@.powa_check_dropped_extensions() ;

CREATE OR REPLACE FUNCTION @extschema@.powa_prevent_concurrent_snapshot(_srvid integer = 0)
RETURNS void
AS $PROC$
DECLARE
    v_state   text;
    v_msg     text;
    v_detail  text;
    v_hint    text;
    v_context text;
BEGIN
    BEGIN
        PERFORM 1
        FROM @extschema@.powa_snapshot_metas
        WHERE srvid = _srvid
        FOR UPDATE NOWAIT;
    EXCEPTION
    WHEN lock_not_available THEN
        RAISE EXCEPTION 'Could not lock the powa_snapshot_metas record, '
        'a concurrent snapshot is probably running';
    WHEN OTHERS THEN
        GET STACKED DIAGNOSTICS
            v_state   = RETURNED_SQLSTATE,
            v_msg     = MESSAGE_TEXT,
            v_detail  = PG_EXCEPTION_DETAIL,
            v_hint    = PG_EXCEPTION_HINT,
            v_context = PG_EXCEPTION_CONTEXT;
        RAISE EXCEPTION 'Failed to lock the powa_snapshot_metas record:
            state  : %
            message: %
            detail : %
            hint   : %
            context: %', v_state, v_msg, v_detail, v_hint, v_context;
    END;
END;
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_prevent_concurrent_snapshot() */

CREATE OR REPLACE FUNCTION @extschema@.powa_take_snapshot(_srvid integer = 0) RETURNS integer
AS $PROC$
DECLARE
  purgets timestamp with time zone;
  purge_seq  bigint;
  r          record;
  v_state    text;
  v_msg      text;
  v_detail   text;
  v_hint     text;
  v_context  text;
  v_title    text = 'PoWA - ';
  v_rowcount bigint;
  v_nb_err int = 0;
  v_errs     text[] = '{}';
  v_pattern  text = '@extschema@.powa_take_snapshot(%s): function %s.%I failed:
              state  : %s
              message: %s
              detail : %s
              hint   : %s
              context: %s';
  v_pattern_simple text = '@extschema@.powa_take_snapshot(%s): function %s.%I failed: %s';

  v_pattern_cat  text = '@extschema@.powa_take_snapshot(%s): function @extschema@.powa_catalog_generic_snapshot for catalog %s failed:
              state  : %s
              message: %s
              detail : %s
              hint   : %s
              context: %s';
  v_pattern_cat_simple text = '@extschema@.powa_take_snapshot(%s): function @extschema@.powa_catalog_generic_snapshot for catalog %s failed: %s';
  v_coalesce bigint;
  v_catname text;
BEGIN
    PERFORM set_config('application_name',
        v_title || ' snapshot database list',
        false);
    PERFORM @extschema@.powa_log('start of powa_take_snapshot(' || _srvid || ')');

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    UPDATE @extschema@.powa_snapshot_metas
    SET coalesce_seq = coalesce_seq + 1,
        errors = NULL,
        snapts = now()
    WHERE srvid = _srvid
    RETURNING coalesce_seq INTO purge_seq;

    PERFORM @extschema@.powa_log(format('coalesce_seq(%s): %s', _srvid, purge_seq));

    IF (_srvid = 0) THEN
        SELECT current_setting('powa.coalesce') INTO v_coalesce;
    ELSE
        SELECT powa_coalesce
        FROM @extschema@.powa_servers
        WHERE id = _srvid
        INTO v_coalesce;
    END IF;

    -- For all enabled snapshot functions in the powa_functions table, execute
    FOR r IN SELECT CASE external
                WHEN true THEN quote_ident(nsp.nspname)
                ELSE '@extschema@'
             END AS schema, function_name AS funcname
             FROM @extschema@.powa_all_functions AS pf
             LEFT JOIN pg_extension AS ext ON pf.kind = 'extension'
                AND ext.extname = pf.name
             LEFT JOIN pg_namespace AS nsp ON nsp.oid = ext.extnamespace
             WHERE operation='snapshot'
             AND enabled
             AND srvid = _srvid
             ORDER BY priority, name
    LOOP
      -- Call all of them, for the current srvid
      BEGIN
        PERFORM @extschema@.powa_log(format('calling snapshot function: %s.%I',
                                     r.schema, r.funcname));
        PERFORM set_config('application_name',
            v_title || quote_ident(r.funcname) || '(' || _srvid || ')', false);

        EXECUTE format('SELECT %s.%I(%s)', r.schema, r.funcname, _srvid);
      EXCEPTION
        WHEN OTHERS THEN
          GET STACKED DIAGNOSTICS
              v_state   = RETURNED_SQLSTATE,
              v_msg     = MESSAGE_TEXT,
              v_detail  = PG_EXCEPTION_DETAIL,
              v_hint    = PG_EXCEPTION_HINT,
              v_context = PG_EXCEPTION_CONTEXT;

          RAISE warning '%', format(v_pattern, _srvid, r.schema, r.funcname,
            v_state, v_msg, v_detail, v_hint, v_context);

          v_errs := array_append(v_errs, format(v_pattern_simple, _srvid,
                r.schema, r.funcname, v_msg));

          v_nb_err = v_nb_err + 1;
      END;
    END LOOP;

    -- Coalesce datas if needed
    IF ( (purge_seq % v_coalesce ) = 0 )
    THEN
      PERFORM @extschema@.powa_log(
        format('coalesce needed, srvid: %s - seq: %s - coalesce seq: %s',
        _srvid, purge_seq, v_coalesce ));

      FOR r IN SELECT CASE external
                  WHEN true THEN quote_ident(nsp.nspname)
                  ELSE '@extschema@'
               END AS schema, function_name AS funcname
               FROM @extschema@.powa_all_functions AS pf
               LEFT JOIN pg_extension AS ext ON pf.kind = 'extension'
                  AND ext.extname = pf.name
               LEFT JOIN pg_namespace AS nsp ON nsp.oid = ext.extnamespace
               WHERE operation='aggregate'
               AND enabled
               AND srvid = _srvid
               ORDER BY priority, name
      LOOP
        -- Call all of them, for the current srvid
        BEGIN
          PERFORM @extschema@.powa_log(format('calling aggregate function: %s.%I(%s)',
                r.schema, r.funcname, _srvid));

          PERFORM set_config('application_name',
              v_title || quote_ident(r.funcname) || '(' || _srvid || ')',
              false);

          EXECUTE format('SELECT %s.%I(%s)', r.schema, r.funcname, _srvid);
        EXCEPTION
          WHEN OTHERS THEN
            GET STACKED DIAGNOSTICS
                v_state   = RETURNED_SQLSTATE,
                v_msg     = MESSAGE_TEXT,
                v_detail  = PG_EXCEPTION_DETAIL,
                v_hint    = PG_EXCEPTION_HINT,
                v_context = PG_EXCEPTION_CONTEXT;

            RAISE warning '%', format(v_pattern, _srvid, r.schema, r.funcname,
                v_state, v_msg, v_detail, v_hint, v_context);

            v_errs := array_append(v_errs, format(v_pattern_simple, _srvid,
                    r.schema, r.funcname, v_msg));

            v_nb_err = v_nb_err + 1;
        END;
      END LOOP;

      PERFORM set_config('application_name',
          v_title || 'UPDATE powa_snapshot_metas.aggets',
          false);
      UPDATE @extschema@.powa_snapshot_metas
      SET aggts = now()
      WHERE srvid = _srvid;
    END IF;

    -- We also purge, at the pass after the coalesce
    IF ( (purge_seq % v_coalesce) = 1 )
    THEN
      PERFORM @extschema@.powa_log(
        format('purge needed, srvid: %s - seq: %s coalesce seq: %s',
        _srvid, purge_seq, v_coalesce));

      FOR r IN SELECT CASE external
                    WHEN true THEN quote_ident(nsp.nspname)
                    ELSE '@extschema@'
               END AS schema, function_name AS funcname
               FROM @extschema@.powa_all_functions AS pf
               LEFT JOIN pg_extension AS ext ON pf.kind = 'extension'
                  AND ext.extname = pf.name
               LEFT JOIN pg_namespace AS nsp ON nsp.oid = ext.extnamespace
               WHERE operation='purge'
               AND enabled
               AND srvid = _srvid
               ORDER BY priority, name
      LOOP
        -- Call all of them, for the current srvid
        BEGIN
          PERFORM @extschema@.powa_log(format('calling purge function: %s.%I(%s)',
                r.schema, r.funcname, _srvid));
          PERFORM set_config('application_name',
              v_title || quote_ident(r.funcname) || '(' || _srvid || ')',
              false);

          EXECUTE format('SELECT %s.%I(%s)', r.schema, r.funcname, _srvid);
        EXCEPTION
          WHEN OTHERS THEN
            GET STACKED DIAGNOSTICS
                v_state   = RETURNED_SQLSTATE,
                v_msg     = MESSAGE_TEXT,
                v_detail  = PG_EXCEPTION_DETAIL,
                v_hint    = PG_EXCEPTION_HINT,
                v_context = PG_EXCEPTION_CONTEXT;

            RAISE warning '%', format(v_pattern, _srvid, r.schema, r.funcname,
                v_state, v_msg, v_detail, v_hint, v_context);

            v_errs := array_append(v_errs, format(v_pattern_simple, _srvid,
                  r.schema, r.funcname, v_msg));

            v_nb_err = v_nb_err + 1;
        END;
      END LOOP;

      PERFORM set_config('application_name',
          v_title || 'UPDATE powa_snapshot_metas.purgets',
          false);
      UPDATE @extschema@.powa_snapshot_metas
      SET purgets = now()
      WHERE srvid = _srvid;
    END IF;

    -- and finally we call the snapshot function for the per-db catalog import,
    -- if this is a remote server
    IF (_srvid != 0) THEN
      FOR v_catname IN SELECT catname FROM @extschema@.powa_catalogs ORDER BY priority
      LOOP
        PERFORM @extschema@.powa_log(format('calling catalog function: %s.%I(%s, %s)',
              '@extschema@', 'powa_catalog_generic_snapshot', _srvid, v_catname));
        PERFORM set_config('application_name',
            v_title || quote_ident('powa_catalog_generic_snapshot')
                    || '(' || _srvid || ', ' || v_catname || ')', false);

        BEGIN
          PERFORM @extschema@.powa_catalog_generic_snapshot(_srvid, v_catname);
        EXCEPTION
          WHEN OTHERS THEN
            GET STACKED DIAGNOSTICS
                v_state   = RETURNED_SQLSTATE,
                v_msg     = MESSAGE_TEXT,
                v_detail  = PG_EXCEPTION_DETAIL,
                v_hint    = PG_EXCEPTION_HINT,
                v_context = PG_EXCEPTION_CONTEXT;

            RAISE warning '%', format(v_pattern_cat, _srvid, v_catname,
                v_state, v_msg, v_detail, v_hint, v_context);

            v_errs := array_append(v_errs, format(v_pattern_cat_simple, _srvid,
                  v_catname, v_msg));

            v_nb_err = v_nb_err + 1;
        END;
      END LOOP;
    END IF;

    IF (v_nb_err > 0) THEN
      UPDATE @extschema@.powa_snapshot_metas
      SET errors = v_errs
      WHERE srvid = _srvid;
    END IF;

    PERFORM @extschema@.powa_log('end of powa_take_snapshot(' || _srvid || ')');
    PERFORM set_config('application_name',
        v_title || 'snapshot finished',
        false);

    return v_nb_err;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_take_snapshot(int) */

CREATE OR REPLACE FUNCTION @extschema@.powa_databases_src(IN _srvid integer,
    OUT oid oid,
    OUT datname name)
RETURNS SETOF record
STABLE
AS $PROC$
BEGIN
    IF (_srvid = 0) THEN
        RETURN QUERY SELECT d.oid, d.datname
        FROM pg_catalog.pg_database d;
    ELSE
        RETURN QUERY SELECT d.oid, d.datname
        FROM @extschema@.powa_databases_src_tmp d
        WHERE srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_databases_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_databases_snapshot(_srvid integer)
RETURNS void AS $PROC$
DECLARE
    result boolean;
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_databases_snapshot', _srvid);
    v_rowcount    bigint;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    -- Keep track of existing databases
    PERFORM @extschema@.powa_log('Maintaining database list...');

    WITH missing AS (
        SELECT _srvid AS srvid, d.oid, d.datname
        FROM @extschema@.powa_databases_src(_srvid) d
        LEFT JOIN @extschema@.powa_databases p ON d.oid = p.oid AND p.srvid = _srvid
        WHERE p.oid IS NULL
    )
    INSERT INTO @extschema@.powa_databases
    SELECT * FROM missing;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('missing db: %s', v_rowcount));

    -- Keep track of renamed databases
    WITH renamed AS (
        SELECT d.oid, d.datname
        FROM @extschema@.powa_databases_src(_srvid) AS d
        JOIN @extschema@.powa_databases AS p ON d.oid = p.oid AND p.srvid = _srvid
        WHERE d.datname != p.datname
    )
    UPDATE @extschema@.powa_databases AS p
    SET datname = r.datname
    FROM renamed AS r
    WHERE p.oid = r.oid
      AND p.srvid = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('renamed db: %s', v_rowcount));

    -- Keep track of when databases are dropped
    WITH dropped AS (
        SELECT p.oid
        FROM @extschema@.powa_databases p
        LEFT JOIN @extschema@.powa_databases_src(_srvid) d ON p.oid = d.oid
        WHERE d.oid IS NULL
          AND p.dropped IS NULL
          AND p.srvid = _srvid)
    UPDATE @extschema@.powa_databases p
    SET dropped = now()
    FROM dropped d
    WHERE p.oid = d.oid
      AND p.srvid = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('dropped db: %s', v_rowcount));

    IF (_srvid != 0) THEN
        DELETE FROM @extschema@.powa_databases_src_tmp WHERE srvid = _srvid;
    END IF;
END;
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_databases_snapshot */

CREATE OR REPLACE FUNCTION @extschema@.powa_statements_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT userid oid,
    OUT dbid oid,
    OUT toplevel boolean,
    OUT queryid bigint,
    OUT query text,
    OUT calls bigint,
    OUT total_exec_time double precision,
    OUT rows bigint,
    OUT shared_blks_hit bigint,
    OUT shared_blks_read bigint,
    OUT shared_blks_dirtied bigint,
    OUT shared_blks_written bigint,
    OUT local_blks_hit bigint,
    OUT local_blks_read bigint,
    OUT local_blks_dirtied bigint,
    OUT local_blks_written bigint,
    OUT temp_blks_read bigint,
    OUT temp_blks_written bigint,
    OUT shared_blk_read_time double precision,
    OUT shared_blk_write_time double precision,
    OUT local_blk_read_time double precision,
    OUT local_blk_write_time double precision,
    OUT temp_blk_read_time double precision,
    OUT temp_blk_write_time double precision,
    OUT plans bigint,
    OUT total_plan_time float8,
    OUT wal_records bigint,
    OUT wal_fpi bigint,
    OUT wal_bytes numeric,
    OUT jit_functions bigint,
    OUT jit_generation_time double precision,
    OUT jit_inlining_count bigint,
    OUT jit_inlining_time double precision,
    OUT jit_optimization_count bigint,
    OUT jit_optimization_time double precision,
    OUT jit_emission_count bigint,
    OUT jit_emission_time double precision,
    OUT jit_deform_count bigint,
    OUT jit_deform_time double precision
)
RETURNS SETOF record
STABLE
AS $PROC$
DECLARE
    v_pgss integer[];
    v_nsp text;
BEGIN
    IF (_srvid = 0) THEN
        SELECT regexp_split_to_array(extversion, E'\\.'), nspname
            INTO STRICT v_pgss, v_nsp
        FROM pg_catalog.pg_extension e
        JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace
        WHERE e.extname = 'pg_stat_statements';

        -- pgss 1.11+, blk_(read|write)_time split in (shared|local_temp) and
        -- jit_deform_* added
        IF (v_pgss[1] = 1 AND v_pgss[2] >= 11) THEN
            RETURN QUERY EXECUTE format($$SELECT now(),
                pgss.userid, pgss.dbid, pgss.toplevel, pgss.queryid, pgss.query,
                pgss.calls, pgss.total_exec_time,
                pgss.rows, pgss.shared_blks_hit,
                pgss.shared_blks_read, pgss.shared_blks_dirtied,
                pgss.shared_blks_written, pgss.local_blks_hit,
                pgss.local_blks_read, pgss.local_blks_dirtied,
                pgss.local_blks_written, pgss.temp_blks_read,
                pgss.temp_blks_written,
                pgss.shared_blk_read_time, pgss.shared_blk_write_time,
                pgss.local_blk_read_time, pgss.local_blk_write_time,
                pgss.temp_blk_read_time, pgss.temp_blk_write_time,
                pgss.plans, pgss.total_plan_time,
                pgss.wal_records, pgss.wal_fpi, pgss.wal_bytes,
                pgss.jit_functions, pgss.jit_generation_time,
                pgss.jit_inlining_count, pgss.jit_inlining_time,
                pgss.jit_optimization_count, pgss.jit_optimization_time,
                pgss.jit_emission_count, pgss.jit_emission_time,
                pgss.jit_deform_count, pgss.jit_deform_time
            FROM %I.pg_stat_statements pgss
            JOIN pg_catalog.pg_database d ON d.oid = pgss.dbid
            JOIN pg_catalog.pg_roles r ON pgss.userid = r.oid
            WHERE pgss.query !~* '^[[:space:]]*(DEALLOCATE|BEGIN|PREPARE TRANSACTION|COMMIT PREPARED|ROLLBACK PREPARED)'
            AND NOT (r.rolname = ANY (string_to_array(
                        @extschema@.powa_get_guc('powa.ignored_users', ''),
                        ',')))
            $$, v_nsp);
        -- pgss 1.10+, toplevel and some jit fields added
        ELSIF (v_pgss[1] = 1 AND v_pgss[2] >= 10) THEN
            RETURN QUERY EXECUTE format($$SELECT now(),
                pgss.userid, pgss.dbid, pgss.toplevel, pgss.queryid, pgss.query,
                pgss.calls, pgss.total_exec_time,
                pgss.rows, pgss.shared_blks_hit,
                pgss.shared_blks_read, pgss.shared_blks_dirtied,
                pgss.shared_blks_written, pgss.local_blks_hit,
                pgss.local_blks_read, pgss.local_blks_dirtied,
                pgss.local_blks_written, pgss.temp_blks_read,
                pgss.temp_blks_written,
                pgss.blk_read_time AS shared_blk_read_time,
                pgss.blk_write_time AS shared_blk_write_time,
                0::double precision AS local_blk_read_time,
                0::double precision AS local_blk_write_time,
                0::double precision AS temp_blk_read_time,
                0::double precision AS temp_blk_write_time,
                pgss.plans, pgss.total_plan_time,
                pgss.wal_records, pgss.wal_fpi, pgss.wal_bytes,
                pgss.jit_functions, pgss.jit_generation_time,
                pgss.jit_inlining_count, pgss.jit_inlining_time,
                pgss.jit_optimization_count, pgss.jit_optimization_time,
                pgss.jit_emission_count, pgss.jit_emission_time,
                0::bigint AS jit_deform_count, 0::double precision AS jit_deform_time
            FROM %I.pg_stat_statements pgss
            JOIN pg_catalog.pg_database d ON d.oid = pgss.dbid
            JOIN pg_catalog.pg_roles r ON pgss.userid = r.oid
            WHERE pgss.query !~* '^[[:space:]]*(DEALLOCATE|BEGIN|PREPARE TRANSACTION|COMMIT PREPARED|ROLLBACK PREPARED)'
            AND NOT (r.rolname = ANY (string_to_array(
                        @extschema@.powa_get_guc('powa.ignored_users', ''),
                        ',')))
            $$, v_nsp);
        -- pgss 1.8+, planning counters added
        ELSIF (v_pgss[1] = 1 AND v_pgss[2] >= 8) THEN
            RETURN QUERY EXECUTE format($$SELECT now(),
                pgss.userid, pgss.dbid, true::boolean, pgss.queryid, pgss.query,
                pgss.calls, pgss.total_exec_time,
                pgss.rows, pgss.shared_blks_hit,
                pgss.shared_blks_read, pgss.shared_blks_dirtied,
                pgss.shared_blks_written, pgss.local_blks_hit,
                pgss.local_blks_read, pgss.local_blks_dirtied,
                pgss.local_blks_written, pgss.temp_blks_read,
                pgss.temp_blks_written,
                pgss.blk_read_time AS shared_blk_read_time,
                pgss.blk_write_time AS shared_blk_write_time,
                0::double precision AS local_blk_read_time,
                0::double precision AS local_blk_write_time,
                0::double precision AS temp_blk_read_time,
                0::double precision AS temp_blk_write_time,
                pgss.plans, pgss.total_plan_time,
                pgss.wal_records, pgss.wal_fpi, pgss.wal_bytes,
                0::bigint AS jit_functions, 0::double precision AS jit_generation_time,
                0::bigint AS jit_inlining_count, 0::double precision AS jit_inlining_time,
                0::bigint AS jit_optimization_count, 0::double precision AS jit_optimization_time,
                0::bigint AS jit_emission_count, 0::double precision AS jit_emission_time,
                0::bigint AS jit_deform_count, 0::double precision AS jit_deform_time
            FROM %I.pg_stat_statements pgss
            JOIN pg_catalog.pg_database d ON d.oid = pgss.dbid
            JOIN pg_catalog.pg_roles r ON pgss.userid = r.oid
            WHERE pgss.query !~* '^[[:space:]]*(DEALLOCATE|BEGIN|PREPARE TRANSACTION|COMMIT PREPARED|ROLLBACK PREPARED)'
            AND NOT (r.rolname = ANY (string_to_array(
                        @extschema@.powa_get_guc('powa.ignored_users', ''),
                        ',')))
            $$, v_nsp);
        ELSE
            RETURN QUERY EXECUTE format($$SELECT now(),
                pgss.userid, pgss.dbid, true::boolean, pgss.queryid, pgss.query,
                pgss.calls, pgss.total_time,
                pgss.rows, pgss.shared_blks_hit,
                pgss.shared_blks_read, pgss.shared_blks_dirtied,
                pgss.shared_blks_written, pgss.local_blks_hit,
                pgss.local_blks_read, pgss.local_blks_dirtied,
                pgss.local_blks_written, pgss.temp_blks_read,
                pgss.temp_blks_written,
                pgss.blk_read_time AS shared_blk_read_time,
                pgss.blk_write_time AS shared_blk_write_time,
                0::double precision AS local_blk_read_time,
                0::double precision AS local_blk_write_time,
                0::double precision AS temp_blk_read_time,
                0::double precision AS temp_blk_write_time,
                0::bigint AS plans, 0::double precision AS total_plan_time,
                0::bigint AS wal_records, 0::bigint AS wal_fpi, 0::numeric AS wal_bytes,
                0::bigint AS jit_functions, 0::double precision AS jit_generation_time,
                0::bigint AS jit_inlining_count, 0::double precision AS jit_inlining_time,
                0::bigint AS jit_optimization_count, 0::double precision AS jit_optimization_time,
                0::bigint AS jit_emission_count, 0::double precision AS jit_emission_time,
                0::bigint AS jit_deform_count, 0::double precision AS jit_deform_time
            FROM %I.pg_stat_statements pgss
            JOIN pg_catalog.pg_database d ON d.oid = pgss.dbid
            JOIN pg_catalog.pg_roles r ON pgss.userid = r.oid
            WHERE pgss.query !~* '^[[:space:]]*(DEALLOCATE|BEGIN|PREPARE TRANSACTION|COMMIT PREPARED|ROLLBACK PREPARED)'
            AND NOT (r.rolname = ANY (string_to_array(
                        @extschema@.powa_get_guc('powa.ignored_users', ''),
                        ',')))
            $$, v_nsp);
        END IF;
    ELSE
        RETURN QUERY SELECT pgss.ts,
            pgss.userid, pgss.dbid, pgss.toplevel, pgss.queryid, pgss.query,
            pgss.calls, pgss.total_exec_time,
            pgss.rows, pgss.shared_blks_hit,
            pgss.shared_blks_read, pgss.shared_blks_dirtied,
            pgss.shared_blks_written, pgss.local_blks_hit,
            pgss.local_blks_read, pgss.local_blks_dirtied,
            pgss.local_blks_written, pgss.temp_blks_read,
            pgss.temp_blks_written,
            pgss.shared_blk_read_time, pgss.shared_blk_write_time,
            pgss.local_blk_read_time, pgss.local_blk_write_time,
            pgss.temp_blk_read_time, pgss.temp_blk_write_time,
            pgss.plans, pgss.total_plan_time,
            pgss.wal_records, pgss.wal_fpi, pgss.wal_bytes,
            pgss.jit_functions, pgss.jit_generation_time,
            pgss.jit_inlining_count, pgss.jit_inlining_time,
            pgss.jit_optimization_count, pgss.jit_optimization_time,
            pgss.jit_emission_count, pgss.jit_emission_time,
            pgss.jit_deform_count, pgss.jit_deform_time
        FROM @extschema@.powa_statements_src_tmp pgss WHERE srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_statements_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_statements_snapshot(_srvid integer) RETURNS void AS $PROC$
DECLARE
    result boolean;
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_statements_snapshot', _srvid);
    v_rowcount    bigint;
BEGIN
    -- In this function, we capture statements, and also aggregate counters by database
    -- so that the first screens of powa stay reactive even though there may be thousands
    -- of different statements
    -- We only capture databases that are still there
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    WITH capture AS(
        SELECT *
        FROM @extschema@.powa_statements_src(_srvid)
    ),
    mru as (UPDATE @extschema@.powa_statements set last_present_ts = now()
            FROM capture
            WHERE @extschema@.powa_statements.queryid = capture.queryid
              AND @extschema@.powa_statements.dbid = capture.dbid
              AND @extschema@.powa_statements.userid = capture.userid
              AND @extschema@.powa_statements.srvid = _srvid
    ),
    missing_statements AS(
        INSERT INTO @extschema@.powa_statements (srvid, queryid, dbid, userid, query)
            SELECT _srvid, queryid, dbid, userid, min(query)
            FROM capture c
            WHERE NOT EXISTS (SELECT 1
                              FROM @extschema@.powa_statements ps
                              WHERE ps.queryid = c.queryid
                              AND ps.dbid = c.dbid
                              AND ps.userid = c.userid
                              AND ps.srvid = _srvid
            )
            GROUP BY queryid, dbid, userid
    ),

    by_query AS (
        INSERT INTO @extschema@.powa_statements_history_current (srvid, queryid,
                dbid, toplevel, userid, record)
            SELECT _srvid, queryid, dbid, toplevel, userid,
            ROW(
                ts, calls, total_exec_time, rows,
                shared_blks_hit, shared_blks_read, shared_blks_dirtied,
                shared_blks_written, local_blks_hit, local_blks_read,
                local_blks_dirtied, local_blks_written, temp_blks_read,
                temp_blks_written,
                shared_blk_read_time, shared_blk_write_time,
                local_blk_read_time, local_blk_write_time,
                temp_blk_read_time, temp_blk_write_time,
                plans, total_plan_time,
                wal_records, wal_fpi, wal_bytes,
                jit_functions, jit_generation_time,
                jit_inlining_count, jit_inlining_time,
                jit_optimization_count, jit_optimization_time,
                jit_emission_count, jit_emission_time,
                jit_deform_count, jit_deform_time
            )::@extschema@.powa_statements_history_record AS record
            FROM capture
    ),

    by_database AS (
        INSERT INTO @extschema@.powa_statements_history_current_db (srvid, dbid, record)
            SELECT _srvid, dbid,
            ROW(
                ts, sum(calls),
                sum(total_exec_time), sum(rows), sum(shared_blks_hit),
                sum(shared_blks_read), sum(shared_blks_dirtied),
                sum(shared_blks_written), sum(local_blks_hit),
                sum(local_blks_read), sum(local_blks_dirtied),
                sum(local_blks_written), sum(temp_blks_read),
                sum(temp_blks_written),
                sum(shared_blk_read_time), sum(shared_blk_write_time),
                sum(local_blk_read_time), sum(local_blk_write_time),
                sum(temp_blk_read_time), sum(temp_blk_write_time),
                sum(plans), sum(total_plan_time),
                sum(wal_records), sum(wal_fpi), sum(wal_bytes),
                sum(jit_functions), sum(jit_generation_time),
                sum(jit_inlining_count), sum(jit_inlining_time),
                sum(jit_optimization_count), sum(jit_optimization_time),
                sum(jit_emission_count), sum(jit_emission_time),
                sum(jit_deform_count), sum(jit_deform_time)
            )::@extschema@.powa_statements_history_record AS record
            FROM capture
            GROUP BY dbid, ts
    )

    SELECT count(*) INTO v_rowcount
    FROM capture;

    PERFORM @extschema@.powa_log(format('%s - rowcount: %s',
            v_funcname, v_rowcount));

    IF (_srvid != 0) THEN
        DELETE FROM @extschema@.powa_statements_src_tmp WHERE srvid = _srvid;
    END IF;

    result := true; -- For now we don't care. jhat could we do on error except crash anyway?
END;
$PROC$ language plpgsql; /* end of powa_statements_snapshot */

CREATE OR REPLACE FUNCTION @extschema@.powa_user_functions_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT dbid oid,
    OUT funcid oid,
    OUT calls bigint,
    OUT total_time double precision,
    OUT self_time double precision
) RETURNS SETOF record
STABLE
AS $PROC$
BEGIN
    IF (_srvid = 0) THEN
        RETURN QUERY SELECT now(), d.oid, r.funcid, r.calls, r.total_time,
            r.self_time
        FROM pg_catalog.pg_database d, @extschema@.powa_stat_user_functions(oid) r;
    ELSE
        RETURN QUERY SELECT r.ts, r.dbid, r.funcid, r.calls, r.total_time,
            r.self_time
        FROM @extschema@.powa_user_functions_src_tmp r
        WHERE r.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_user_functions_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_user_functions_snapshot(_srvid integer) RETURNS void AS $PROC$
DECLARE
    result boolean;
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_user_functions_snapshot', _srvid);
    v_rowcount    bigint;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    -- Insert cluster-wide user function statistics
    WITH func AS (
        SELECT *
        FROM @extschema@.powa_user_functions_src(_srvid)
    ),

    by_function AS (
        INSERT INTO @extschema@.powa_user_functions_history_current
            (srvid, dbid, funcid, record)
            SELECT _srvid, dbid, funcid,
            ROW(ts, calls, total_time, self_time
            )::@extschema@.powa_user_functions_history_record AS record
            FROM func
    ),

    by_database AS (
        INSERT INTO @extschema@.powa_user_functions_history_current_db
            (srvid, dbid, record)
            SELECT _srvid AS srvid, dbid,
            ROW(ts, sum(calls), sum(total_time), sum(self_time)
            )::@extschema@.powa_user_functions_history_record AS record
            FROM func
            GROUP BY srvid, dbid, ts
    )

    SELECT COUNT(*) INTO v_rowcount
    FROM func;

    PERFORM @extschema@.powa_log(format('%s - rowcount: %s',
            v_funcname, v_rowcount));

    IF (_srvid != 0) THEN
        DELETE FROM @extschema@.powa_user_functions_src_tmp WHERE srvid = _srvid;
    END IF;

    result := true;
END;
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_user_functions_snapshot */

CREATE OR REPLACE FUNCTION @extschema@.powa_all_indexes_snapshot(_srvid integer) RETURNS void AS $PROC$
DECLARE
    result boolean;
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_all_indexes_snapshot', _srvid);
    v_rowcount    bigint;
BEGIN
    ASSERT _srvid != 0, 'db module functions can only be called for remote servers';

    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    -- Insert cluster-wide index statistics
    WITH rel AS (
        SELECT *
        FROM @extschema@.powa_all_indexes_src_tmp
    ),

    by_relation AS (
        INSERT INTO @extschema@.powa_all_indexes_history_current
            (srvid, dbid, relid, indexrelid, record)
            SELECT _srvid, dbid, relid, indexrelid,
            ROW(ts,
                idx_size,
                idx_scan, last_idx_scan, idx_tup_read, idx_tup_fetch,
                idx_blks_read, idx_blks_hit
            )::@extschema@.powa_all_indexes_history_record AS record
            FROM rel
    ),

    by_database AS (
        INSERT INTO @extschema@.powa_all_indexes_history_current_db
        (srvid, dbid, record)
            SELECT _srvid AS srvid, dbid,
            ROW(ts,
                sum(idx_size),
                sum(idx_scan), sum(idx_tup_read), sum(idx_tup_fetch),
                sum(idx_blks_read), sum(idx_blks_hit)
            )::@extschema@.powa_all_indexes_history_db_record
            FROM rel
            GROUP BY srvid, dbid, ts
    )

    SELECT COUNT(*) into v_rowcount
    FROM rel;

    PERFORM @extschema@.powa_log(format('%s - rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_all_indexes_src_tmp WHERE srvid = _srvid;

    result := true;
END;
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_all_indexes_snapshot */

CREATE OR REPLACE FUNCTION @extschema@.powa_all_tables_snapshot(_srvid integer) RETURNS void AS $PROC$
DECLARE
    result boolean;
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_all_tables_snapshot', _srvid);
    v_rowcount    bigint;
BEGIN
    ASSERT _srvid != 0, 'db module functions can only be called for remote servers';

    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    -- Insert cluster-wide relation statistics
    WITH rel AS (
        SELECT *
        FROM @extschema@.powa_all_tables_src_tmp
    ),

    by_relation AS (
        INSERT INTO @extschema@.powa_all_tables_history_current
            (srvid, dbid, relid, record)
            SELECT _srvid, dbid, relid,
            ROW(ts,
                tbl_size,
                seq_scan, last_seq_scan, seq_tup_read, idx_scan,
                last_idx_scan, n_tup_ins, n_tup_upd, n_tup_del, n_tup_hot_upd,
                n_tup_newpage_upd, n_liv_tup, n_dead_tup, n_mod_since_analyze,
                n_ins_since_vacuum, last_vacuum, last_autovacuum, last_analyze,
                last_autoanalyze, vacuum_count, autovacuum_count,
                analyze_count, autoanalyze_count, heap_blks_read,
                heap_blks_hit, idx_blks_read, idx_blks_hit, toast_blks_read,
                toast_blks_hit, tidx_blks_read,
                tidx_blks_hit
            )::@extschema@.powa_all_tables_history_record AS record
            FROM rel
    ),

    by_database AS (
        INSERT INTO @extschema@.powa_all_tables_history_current_db
            (srvid, dbid, record)
            SELECT _srvid AS srvid, dbid,
            ROW(ts,
                sum(tbl_size),
                sum(seq_scan), sum(seq_tup_read), sum(idx_scan),
                sum(n_tup_ins), sum(n_tup_upd), sum(n_tup_del),
                sum(n_tup_hot_upd), sum(n_tup_newpage_upd), sum(n_liv_tup),
                sum(n_dead_tup), sum(n_mod_since_analyze),
                sum(n_ins_since_vacuum), sum(vacuum_count),
                sum(autovacuum_count), sum(analyze_count),
                sum(autoanalyze_count), sum(heap_blks_read),
                sum(heap_blks_hit), sum(idx_blks_read), sum(idx_blks_hit),
                sum(toast_blks_read), sum(toast_blks_hit), sum(tidx_blks_read),
                sum(tidx_blks_hit)
            )::@extschema@.powa_all_tables_history_db_record
            FROM rel
            GROUP BY srvid, dbid, ts
    )

    SELECT COUNT(*) into v_rowcount
    FROM rel;

    PERFORM @extschema@.powa_log(format('%s - rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_all_tables_src_tmp WHERE srvid = _srvid;

    result := true;
END;
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_all_tables_snapshot */

CREATE OR REPLACE FUNCTION @extschema@.powa_replication_slots_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT slot_name text,
    OUT plugin text,
    OUT slot_type text,
    OUT datoid oid,
    OUT temporary boolean,
    OUT cur_txid xid,
    OUT current_lsn pg_lsn,
    OUT active bool,
    OUT active_pid int,
    OUT slot_xmin xid,
    OUT catalog_xmin xid,
    OUT restart_lsn pg_lsn,
    OUT confirmed_flush_lsn pg_lsn,
    OUT wal_status text,
    OUT safe_wal_size bigint,
    OUT two_phase boolean,
    OUT conflicting boolean
) RETURNS SETOF record STABLE AS $PROC$
DECLARE
    v_txid xid;
    v_current_lsn pg_lsn;
BEGIN
    IF (_srvid = 0) THEN
        IF pg_catalog.pg_is_in_recovery() THEN
            v_txid = NULL;
        ELSE
            v_txid = pg_catalog.txid_current();
        END IF;

        IF current_setting('server_version_num')::int < 100000 THEN
            IF pg_is_in_recovery() THEN
                v_current_lsn := pg_last_xlog_receive_location();
            ELSE
                v_current_lsn := pg_current_xlog_location();
            END IF;
        ELSE
            IF pg_is_in_recovery() THEN
                v_current_lsn := pg_last_wal_receive_lsn();
            ELSE
                v_current_lsn := pg_current_wal_lsn();
            END IF;
        END IF;

        -- We want to always return a row, even if no replication slots is
        -- found, so the UI can properly graph that no slot exists.

        -- conflicting added in pg16
        IF current_setting('server_version_num')::int >= 160000 THEN
            RETURN QUERY SELECT n.now,
                s.slot_name::text AS slot_name, s.plugin::text AS plugin,
                s.slot_type, s.datoid, s.temporary,
                v_txid, v_current_lsn,
                s.active,
                s.active_pid, s.xmin AS slot_xmin, s.catalog_xmin,
                s.restart_lsn, s.confirmed_flush_lsn, s.wal_status,
                s.safe_wal_size, s.two_phase, s.conflicting
            FROM (SELECT now() AS now) n
            LEFT JOIN pg_catalog.pg_replication_slots AS s ON true;
        -- two_phase added in pg14
        ELSIF current_setting('server_version_num')::int >= 140000 THEN
            RETURN QUERY SELECT n.now,
                s.slot_name::text AS slot_name, s.plugin::text AS plugin,
                s.slot_type, s.datoid, s.temporary,
                v_txid, v_current_lsn,
                s.active,
                s.active_pid, s.xmin AS slot_xmin, s.catalog_xmin,
                s.restart_lsn, s.confirmed_flush_lsn, s.wal_status,
                s.safe_wal_size, s.two_phase, false AS conflicting
            FROM (SELECT now() AS now) n
            LEFT JOIN pg_catalog.pg_replication_slots AS s ON true;
        -- wal_status and safe_wal_size added in pg13
        ELSIF current_setting('server_version_num')::int >= 130000 THEN
            RETURN QUERY SELECT n.now,
                s.slot_name::text AS slot_name, s.plugin::text AS plugin,
                s.slot_type, s.datoid, s.temporary,
                v_txid, v_current_lsn,
                s.active,
                s.active_pid, s.xmin AS slot_xmin, s.catalog_xmin,
                s.restart_lsn, s.confirmed_flush_lsn, s.wal_status,
                s.safe_wal_size, false AS two_phase, false AS conflicting
            FROM (SELECT now() AS now) n
            LEFT JOIN pg_catalog.pg_replication_slots AS s ON true;
        -- temporary added in pg10
        ELSIF current_setting('server_version_num')::int >= 100000 THEN
            RETURN QUERY SELECT n.now,
                s.slot_name::text AS slot_name, s.plugin::text AS plugin,
                s.slot_type, s.datoid, s.temporary,
                v_txid, v_current_lsn,
                s.active,
                s.active_pid, s.xmin AS slot_xmin, s.catalog_xmin,
                s.restart_lsn, s.confirmed_flush_lsn,
                NULL::text as wal_status,
                NULL::bigint as safe_wal_size,
                false AS two_phase, false AS conflicting
            FROM (SELECT now() AS now) n
            LEFT JOIN pg_catalog.pg_replication_slots AS s ON true;
        -- confirmed_flush_lsn added in pg9.6
        ELSIF current_setting('server_version_num')::int >= 90600 THEN
            RETURN QUERY SELECT n.now,
                s.slot_name::text AS slot_name, s.plugin::text AS plugin,
                s.slot_type, s.datoid, false AS temporary,
                v_txid, v_current_lsn,
                s.active,
                s.active_pid, s.xmin AS slot_xmin, s.catalog_xmin,
                s.restart_lsn, s.confirmed_flush_lsn,
                NULL::text as wal_status,
                NULL::bigint as safe_wal_size,
                false AS two_phase, false AS conflicting
            FROM (SELECT now() AS now) n
            LEFT JOIN pg_catalog.pg_replication_slots AS s ON true;
        -- active_pid added in pg9.5
        ELSIF current_setting('server_version_num')::int >= 90500 THEN
            RETURN QUERY SELECT n.now,
                s.slot_name::text AS slot_name, s.plugin::text AS plugin,
                s.slot_type, s.datoid, false AS temporary,
                v_txid, v_current_lsn,
                s.active,
                s.active_pid, s.xmin AS slot_xmin, s.catalog_xmin,
                s.restart_lsn, NULL::pg_lsn AS confirmed_flush_lsn,
                NULL::text as wal_status,
                NULL::bigint as safe_wal_size,
                false AS two_phase, false AS conflicting
            FROM (SELECT now() AS now) n
            LEFT JOIN pg_catalog.pg_replication_slots AS s ON true;
        ELSE
            RETURN QUERY SELECT n.now,
                s.slot_name::text AS slot_name, s.plugin::text AS plugin,
                s.slot_type, s.datoid, false AS temporary,
                v_txid, v_current_lsn,
                s.active,
                NULL::int AS active_pid, s.xmin AS slot_xmin, s.catalog_xmin,
                s.restart_lsn, NULL::pg_lsn AS confirmed_flush_lsn,
                NULL::text as wal_status,
                NULL::bigint as safe_wal_size,
                false AS two_phase, false AS conflicting
            FROM (SELECT now() AS now) n
            LEFT JOIN pg_catalog.pg_replication_slots AS s ON true;
        END IF;
    ELSE
        RETURN QUERY SELECT s.ts,
            s.slot_name, s.plugin,
            s.slot_type, s.datoid, s.temporary,
            s.cur_txid, s.current_lsn,
            s.active,
            s.active_pid, s.xmin AS slot_xmin, s.catalog_xmin,
            s.restart_lsn, s.confirmed_flush_lsn, s.wal_status,
            s.safe_wal_size, s.two_phase, s.conflicting
        FROM @extschema@.powa_replication_slots_src_tmp AS s
        WHERE s.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_replication_slots_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_stat_activity_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT cur_txid xid,
    OUT datid oid,
    OUT pid integer,
    OUT leader_pid integer,
    OUT usesysid oid,
    OUT application_name text,
    OUT client_addr inet,
    OUT backend_start timestamp with time zone,
    OUT xact_start timestamp with time zone,
    OUT query_start timestamp with time zone,
    OUT state_change timestamp with time zone,
    OUT state text,
    OUT backend_xid xid,
    OUT backend_xmin xid,
    OUT query_id bigint,
    OUT backend_type text
) RETURNS SETOF record STABLE AS $PROC$
DECLARE
    txid xid;
BEGIN
    IF (_srvid = 0) THEN
        IF pg_catalog.pg_is_in_recovery() THEN
            txid = NULL;
        ELSE
            txid = pg_catalog.txid_current();
        END IF;

        -- query_id added in pg14
        IF current_setting('server_version_num')::int >= 140000 THEN
            RETURN QUERY SELECT now(),
                txid,
                s.datid, s.pid, s.leader_pid, s.usesysid,
                s.application_name, s.client_addr, s.backend_start,
                s.xact_start,
                s.query_start, s.state_change, s.state, s.backend_xid,
                s.backend_xmin, s.query_id, s.backend_type
            FROM pg_catalog.pg_stat_activity AS s;
        -- leader_pid added in pg13+
        ELSIF current_setting('server_version_num')::int >= 130000 THEN
            RETURN QUERY SELECT now(),
                txid,
                s.datid, s.pid, s.leader_pid, s.usesysid,
                s.application_name, s.client_addr, s.backend_start,
                s.xact_start,
                s.query_start, s.state_change, s.state, s.backend_xid,
                s.backend_xmin, NULL::bigint AS query_id, s.backend_type
            FROM pg_catalog.pg_stat_activity AS s;
        -- backend_type added in pg10+
        ELSIF current_setting('server_version_num')::int >= 100000 THEN
            RETURN QUERY SELECT now(),
                txid,
                s.datid, s.pid, NULL::integer AS leader_pid, s.usesysid,
                s.application_name, s.client_addr, s.backend_start,
                s.xact_start,
                s.query_start, s.state_change, s.state, s.backend_xid,
                s.backend_xmin, NULL::bigint AS query_id, s.backend_type
            FROM pg_catalog.pg_stat_activity AS s;
        ELSE
            RETURN QUERY SELECT now(),
                txid,
                s.datid, s.pid, NULL::integer AS leader_pid, s.usesysid,
                s.application_name, s.client_addr, s.backend_start,
                s.xact_start,
                s.query_start, s.state_change, s.state, s.backend_xid,
                s.backend_xmin, NULL::bigint AS query_id,
                NULL::text AS backend_type
            FROM pg_catalog.pg_stat_activity AS s;
        END IF;
    ELSE
        RETURN QUERY SELECT s.ts,
            s.cur_txid,
            s.datid, s.pid, s.leader_pid, s.usesysid,
            s.application_name, s.client_addr, s.backend_start,
            s.xact_start,
            s.query_start, s.state_change, s.state, s.backend_xid,
            s.backend_xmin, s.query_id, s.backend_type
        FROM @extschema@.powa_stat_activity_src_tmp AS s
        WHERE s.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_stat_activity_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_stat_archiver_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT current_wal text,
    OUT archived_count bigint,
    OUT last_archived_wal text,
    OUT last_archived_time timestamp with time zone,
    OUT failed_count bigint,
    OUT last_failed_wal text,
    OUT last_failed_time timestamp with time zone
) RETURNS SETOF record STABLE AS $PROC$
DECLARE
    v_current_wal text;
BEGIN
    IF (_srvid = 0) THEN
        -- get the current WAL name if possible
        IF pg_is_in_recovery() THEN
            -- there's no reliable way to get either the current WAL offset
            -- (not exposed for pure WAL-shipping replication), nor the
            -- underlying WAL file name for a given WAL offset on a standby
            v_current_wal := NULL;
        ELSE
            IF current_setting('server_version_num')::int < 100000 THEN
                v_current_wal :=  pg_xlogfile_name(pg_current_xlog_location());
            ELSE
                v_current_wal :=  pg_walfile_name(pg_current_wal_lsn());
            END IF;
        END IF;

        RETURN QUERY SELECT now(),
            v_current_wal,
            s.archived_count, s.last_archived_wal, s.last_archived_time,
            s.failed_count, s.last_failed_wal, s.last_failed_time
        FROM pg_catalog.pg_stat_archiver AS s;
    ELSE
        RETURN QUERY SELECT s.ts,
            s.current_wal,
            s.archived_count, s.last_archived_wal, s.last_archived_time,
            s.failed_count, s.last_failed_wal, s.last_failed_time
        FROM @extschema@.powa_stat_archiver_src_tmp AS s
        WHERE s.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql; /* end of powa_stat_archiver_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_stat_bgwriter_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT buffers_clean bigint,
    OUT maxwritten_clean bigint,
    OUT buffers_backend bigint,
    OUT buffers_backend_fsync bigint,
    OUT buffers_alloc bigint
) RETURNS SETOF record STABLE AS $PROC$
BEGIN
    IF (_srvid = 0) THEN
        -- pg17+, buffers_backend* removed.  We maintain them, extracted from
        -- pg_stat_io to make UI job easier.
        IF current_setting('server_version_num')::int >= 170000 THEN
            RETURN QUERY SELECT now(),
                s.buffers_clean,
                s.maxwritten_clean, i.buffers_backend, i.buffers_backend_fsync,
                s.buffers_alloc
            FROM pg_catalog.pg_stat_bgwriter AS s
            CROSS JOIN (
                SELECT sum(writes + extends)::bigint AS buffers_backend,
                    sum(fsyncs)::bigint AS buffers_backend_fsync
                FROM pg_catalog.pg_stat_io
                WHERE backend_type = 'client backend'
            ) AS i;
        ELSE
            RETURN QUERY SELECT now(),
                s.buffers_clean,
                s.maxwritten_clean, s.buffers_backend, s.buffers_backend_fsync,
                s.buffers_alloc
            FROM pg_catalog.pg_stat_bgwriter AS s;
        END IF;
    ELSE
        RETURN QUERY SELECT s.ts,
            s.buffers_clean,
            s.maxwritten_clean,
            s.buffers_backend, s.buffers_backend_fsync,
            s.buffers_alloc
        FROM @extschema@.powa_stat_bgwriter_src_tmp AS s
        WHERE s.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_stat_bgwriter_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_stat_checkpointer_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT num_timed bigint,
    OUT num_requested bigint,
    OUT write_time double precision,
    OUT sync_time double precision,
    OUT buffers_written bigint
) RETURNS SETOF record STABLE AS $PROC$
BEGIN
    IF (_srvid = 0) THEN
        -- pg17+, the pg_stat_checkpointer view is introduced
        IF current_setting('server_version_num')::int >= 170000 THEN
            RETURN QUERY SELECT now(),
                s.num_timed, s.num_requested,
                s.write_time, s.sync_time, s.buffers_written
            FROM pg_catalog.pg_stat_checkpointer AS s;
        -- for older versions, simulate that view getting info from
        -- pg_stat_bgwriter
        ELSE
            RETURN QUERY SELECT now(),
                s.checkpoints_timed AS num_timed,
                s.checkpoints_req AS num_requested,
                s.checkpoint_write_time AS write_time,
                s.checkpoint_sync_time AS sync_time,
                s.buffers_checkpoint AS buffers_written
            FROM pg_catalog.pg_stat_bgwriter AS s;
        END IF;
    ELSE
        RETURN QUERY SELECT s.ts,
            s.num_timed, s.num_requested,
            s.write_time, s.sync_time, s.buffers_written
        FROM @extschema@.powa_stat_checkpointer_src_tmp AS s
        WHERE s.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql; /* end of powa_stat_checkpointer_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_stat_database_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT datid oid,
    OUT numbackends integer,
    OUT xact_commit bigint,
    OUT xact_rollback bigint,
    OUT blks_read bigint,
    OUT blks_hit bigint,
    OUT tup_returned bigint,
    OUT tup_fetched bigint,
    OUT tup_inserted bigint,
    OUT tup_updated bigint,
    OUT tup_deleted bigint,
    OUT conflicts bigint,
    OUT temp_files bigint,
    OUT temp_bytes bigint,
    OUT deadlocks bigint,
    OUT checksum_failures bigint,
    OUT checksum_last_failure timestamp with time zone,
    OUT blk_read_time double precision,
    OUT blk_write_time double precision,
    OUT session_time double precision,
    OUT active_time double precision,
    OUT idle_in_transaction_time double precision,
    OUT sessions bigint,
    OUT sessions_abandoned bigint,
    OUT sessions_fatal bigint,
    OUT sessions_killed bigint,
    OUT stats_reset timestamp with time zone
) RETURNS SETOF record STABLE AS $PROC$
BEGIN
    IF (_srvid = 0) THEN
        -- pg14+, *_time and sessions_* added
        IF current_setting('server_version_num')::int >= 140000 THEN
            RETURN QUERY SELECT now(),
            s.datid, s.numbackends, s.xact_commit, s.xact_rollback,
            s.blks_read, s.blks_hit, s.tup_returned, s.tup_fetched,
            s.tup_inserted, s.tup_updated, s.tup_deleted, s.conflicts,
            s.temp_files, s.temp_bytes, s.deadlocks, s.checksum_failures,
            s.checksum_last_failure, s.blk_read_time, s.blk_write_time,
            s.session_time, s.active_time,
            s.idle_in_transaction_time,
            s.sessions, s.sessions_abandoned,
            s.sessions_fatal, s.sessions_killed,
            s.stats_reset
            FROM pg_catalog.pg_stat_database AS s;
        -- pg12+, checksum_failures and checksum_last_failure added
        ELSIF current_setting('server_version_num')::int >= 120000 THEN
            RETURN QUERY SELECT now(),
            s.datid, s.numbackends, s.xact_commit, s.xact_rollback,
            s.blks_read, s.blks_hit, s.tup_returned, s.tup_fetched,
            s.tup_inserted, s.tup_updated, s.tup_deleted, s.conflicts,
            s.temp_files, s.temp_bytes, s.deadlocks, s.checksum_failures,
            s.checksum_last_failure, s.blk_read_time, s.blk_write_time,
            NULL::double precision AS session_time,
            NULL::double precision AS active_time,
            NULL::double precision AS idle_in_transaction_time,
            NULL::bigint AS sessions, NULL::bigint AS sessions_abandoned,
            NULL::bigint AS sessions_fatal, NULL::bigint AS sessions_killed,
            s.stats_reset
            FROM pg_catalog.pg_stat_database AS s;
        ELSE
            RETURN QUERY SELECT now(),
            s.datid, s.numbackends, s.xact_commit, s.xact_rollback,
            s.blks_read, s.blks_hit, s.tup_returned, s.tup_fetched,
            s.tup_inserted, s.tup_updated, s.tup_deleted, s.conflicts,
            s.temp_files, s.temp_bytes, s.deadlocks,
            0::bigint AS checksum_failures,
            NULL::timestamptz AS checksum_last_failure,
            s.blk_read_time, s.blk_write_time,
            NULL::double precision AS session_time,
            NULL::double precision AS active_time,
            NULL::double precision AS idle_in_transaction_time,
            NULL::bigint AS sessions, NULL::bigint AS sessions_abandoned,
            NULL::bigint AS sessions_fatal, NULL::bigint AS sessions_killed,
            s.stats_reset
            FROM pg_catalog.pg_stat_database AS s;
        END IF;
    ELSE
        RETURN QUERY SELECT s.ts,
            s.datid, s.numbackends, s.xact_commit, s.xact_rollback,
            s.blks_read, s.blks_hit, s.tup_returned, s.tup_fetched,
            s.tup_inserted, s.tup_updated, s.tup_deleted, s.conflicts,
            s.temp_files, s.temp_bytes, s.deadlocks, s.checksum_failures,
            s.checksum_last_failure, s.blk_read_time, s.blk_write_time,
            s.session_time, s.active_time,
            s.idle_in_transaction_time,
            s.sessions, s.sessions_abandoned,
            s.sessions_fatal, s.sessions_killed,
            s.stats_reset
        FROM @extschema@.powa_stat_database_src_tmp AS s
        WHERE s.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_stat_database_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_stat_database_conflicts_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT datid oid,
    OUT confl_tablespace bigint,
    OUT confl_lock bigint,
    OUT confl_snapshot bigint,
    OUT confl_bufferpin bigint,
    OUT confl_deadlock bigint,
    OUT confl_active_logicalslot bigint
) RETURNS SETOF record STABLE AS $PROC$
BEGIN
    IF (_srvid = 0) THEN
        -- pg16+, confl_active_logicalslot added
        IF current_setting('server_version_num')::int >= 160000 THEN
            RETURN QUERY SELECT now(),
            s.datid,
            s.confl_tablespace, s.confl_lock, s.confl_snapshot,
            s.confl_bufferpin, s.confl_deadlock,
            s.confl_active_logicalslot
            FROM pg_catalog.pg_stat_database_conflicts AS s;
        ELSE
            RETURN QUERY SELECT now(),
            s.datid,
            s.confl_tablespace, s.confl_lock, s.confl_snapshot,
            s.confl_bufferpin, s.confl_deadlock,
            0::bigint AS confl_active_logicalslot
            FROM pg_catalog.pg_stat_database_conflicts AS s;
        END IF;
    ELSE
        RETURN QUERY SELECT s.ts,
            s.datid,
            s.confl_tablespace, s.confl_lock, s.confl_snapshot,
            s.confl_bufferpin, s.confl_deadlock,
            s.confl_active_logicalslot
        FROM @extschema@.powa_stat_database_conflicts_src_tmp AS s
        WHERE s.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql; /* end of powa_stat_database_conflicts_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_stat_io_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT backend_type text,
    OUT object text,
    OUT context text,
    OUT reads bigint,
    OUT read_time double precision,
    OUT writes bigint,
    OUT write_time double precision,
    OUT writebacks bigint,
    OUT writeback_time double precision,
    OUT extends bigint,
    OUT extend_time double precision,
    OUT op_bytes bigint,
    OUT hits bigint,
    OUT evictions bigint,
    OUT reuses bigint,
    OUT fsyncs bigint,
    OUT fsync_time double precision,
    OUT stats_reset timestamp with time zone
) RETURNS SETOF record STABLE AS $PROC$
BEGIN
    IF (_srvid = 0) THEN
        -- pg16+, the view is introduced
        IF current_setting('server_version_num')::int >= 160000 THEN
            RETURN QUERY SELECT now(),
            s.backend_type, s.object, s.context,
            s.reads, s.read_time,
            s.writes, s.write_time,
            s.writebacks, s.writeback_time,
            s.extends, s.extend_time,
            s.op_bytes, s.hits,
            s.evictions, s.reuses,
            s.fsyncs, s.fsync_time,
            s.stats_reset
            FROM pg_catalog.pg_stat_io AS s;
        ELSE -- return an empty dataset for pg15- servers
            RETURN QUERY SELECT now(),
            NULL::text AS backend_type, NULL::text AS object,
            NULL::text AS context,
            0::bigint AS reads, 0::double precision AS read_time,
            0::bigint AS writes, 0::double precision AS write_time,
            0::bigint AS writebacks, 0::double precision AS writeback_time,
            0::bigint AS extends, 0::double precision AS extend_time,
            NULL::bigint AS op_bytes, 0::bigint AS hits,
            0::bigint AS evictions, 0::bigint AS reuses,
            0::bigint AS fsyncs, 0::double precision AS fsync_time,
            NULL::timestamp with time zone AS stats_reset
            WHERE false;
        END IF;
    ELSE
        RETURN QUERY SELECT s.ts,
            s.backend_type, s.object, s.context,
            s.reads, s.read_time,
            s.writes, s.write_time,
            s.writebacks, s.writeback_time,
            s.extends, s.extend_time,
            s.op_bytes, s.hits,
            s.evictions, s.reuses,
            s.fsyncs, s.fsync_time,
            s.stats_reset
        FROM @extschema@.powa_stat_io_src_tmp AS s
        WHERE s.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_stat_io_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_stat_replication_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT current_lsn pg_lsn,
    OUT pid integer,
    OUT usename text,
    OUT application_name text,
    OUT client_addr inet,
    OUT backend_start timestamp with time zone,
    OUT backend_xmin xid,
    OUT state text,
    OUT sent_lsn pg_lsn,
    OUT write_lsn pg_lsn,
    OUT flush_lsn pg_lsn,
    OUT replay_lsn pg_lsn,
    OUT write_lag interval,
    OUT flush_lag interval,
    OUT replay_lag interval,
    OUT sync_priority integer,
    OUT sync_state text,
    OUT reply_time timestamp with time zone
) RETURNS SETOF record STABLE AS $PROC$
DECLARE
    v_current_lsn pg_lsn;
BEGIN
    IF (_srvid = 0) THEN
        IF current_setting('server_version_num')::int < 100000 THEN
            IF pg_is_in_recovery() THEN
                v_current_lsn := pg_last_xlog_receive_location();
            ELSE
                v_current_lsn := pg_current_xlog_location();
            END IF;
        ELSE
            IF pg_is_in_recovery() THEN
                v_current_lsn := pg_last_wal_receive_lsn();
            ELSE
                v_current_lsn := pg_current_wal_lsn();
            END IF;
        END IF;

        -- We use a LEFT JOIN on the pg_stat_replication view to make sure that
        -- we always return at least one (all-NULL) row, so client apps can
        -- detect when all the replication connections are down.
        RETURN QUERY SELECT now,
        v_current_lsn,
        s.pid, s.usename::text AS usename, s.application_name, s.client_addr,
        s.backend_start, s.backend_xmin, s.state, s.sent_lsn, s.write_lsn,
        s.flush_lsn, s.replay_lsn, s.write_lag, s.flush_lag, s.replay_lag,
        s.sync_priority, s.sync_state, s.reply_time
        FROM (SELECT now() AS now) n
        LEFT JOIN pg_catalog.pg_stat_replication AS s ON true;
    ELSE
        RETURN QUERY SELECT s.ts,
        s.current_lsn,
        s.pid, s.usename, s.application_name, s.client_addr,
        s.backend_start, s.backend_xmin, s.state, s.sent_lsn, s.write_lsn,
        s.flush_lsn, s.replay_lsn, s.write_lag, s.flush_lag, s.replay_lag,
        s.sync_priority, s.sync_state, s.reply_time
        FROM @extschema@.powa_stat_replication_src_tmp AS s
        WHERE s.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql; /* end of powa_stat_replication_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_stat_slru_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT name text,
    OUT blks_zeroed bigint,
    OUT blks_hit bigint,
    OUT blks_read bigint,
    OUT blks_written bigint,
    OUT blks_exists bigint,
    OUT flushes bigint,
    OUT truncates bigint,
    OUT stats_reset timestamp with time zone
) RETURNS SETOF record STABLE AS $PROC$
BEGIN
    IF (_srvid = 0) THEN
        -- pg13+, the view is introduced
        IF current_setting('server_version_num')::int >= 130000 THEN
            RETURN QUERY SELECT now(),
            s.name,
            s.blks_zeroed, s.blks_hit,
            s.blks_read, s.blks_written, s.blks_exists,
            s.flushes, s.truncates,
            s.stats_reset
            FROM pg_catalog.pg_stat_slru AS s;
        ELSE -- return an empty dataset for pg15- servers
            RETURN QUERY SELECT now(),
            NULL::text AS name,
            0::bigint AS blks_zeroed, 0::bigint AS blks_hit,
            0::bigint AS blks_read, 0::bigint AS blks_written,
            0::bigint AS blks_exists,
            0::bigint AS flushes, 0::bigint as truncates,
            NULL::timestamp with time zone AS stats_reset
            WHERE false;
        END IF;
    ELSE
        RETURN QUERY SELECT s.ts,
            s.name,
            s.blks_zeroed, s.blks_hit,
            s.blks_read, s.blks_written, s.blks_exists,
            s.flushes, s.truncates,
            s.stats_reset
        FROM @extschema@.powa_stat_slru_src_tmp AS s
        WHERE s.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_stat_slru_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_stat_subscription_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT subid oid,
    OUT subname name,
    OUT worker_type text,
    OUT pid integer,
    OUT leader_pid integer,
    OUT relid oid,
    OUT received_lsn pg_lsn,
    OUT last_msg_send_time timestamp with time zone,
    OUT last_msg_receipt_time timestamp with time zone,
    OUT latest_end_lsn pg_lsn,
    OUT latest_end_time timestamp with time zone
) RETURNS SETOF record STABLE AS $PROC$
DECLARE
    v_pg_version_num int;
BEGIN
    IF (_srvid = 0) THEN
        v_pg_version_num := current_setting('server_version_num')::int;

        -- pg17+, worker_type is added
        IF v_pg_version_num >= 170000 THEN
            RETURN QUERY SELECT now(),
            s.subid, s.subname,
            s.worker_type,
            s.pid, s.leader_pid,
            s.relid, s.received_lsn,
            s.last_msg_send_time, s.last_msg_receipt_time,
            s.latest_end_lsn, s.latest_end_time
            FROM pg_catalog.pg_stat_subscription AS s;
        -- pg16+, leader_pid is added
        ELSIF v_pg_version_num >= 160000 THEN
            RETURN QUERY SELECT now(),
            s.subid, s.subname,
            'apply'::text AS worker_type,
            s.pid, s.leader_pid,
            s.relid, s.received_lsn,
            s.last_msg_send_time, s.last_msg_receipt_time,
            s.latest_end_lsn, s.latest_end_time
            FROM pg_catalog.pg_stat_subscription AS s;
        -- pg10+, the view is introduced
        ELSIF v_pg_version_num >= 100000 THEN
            RETURN QUERY SELECT now(),
            s.subid, s.subname,
            'apply'::text AS worker_type,
            s.pid, NULL::integer AS leader_pid,
            s.relid, s.received_lsn,
            s.last_msg_send_time, s.last_msg_receipt_time,
            s.latest_end_lsn, s.latest_end_time
            FROM pg_catalog.pg_stat_subscription AS s;
        ELSE -- return an empty dataset for pg9.6- servers
            RETURN QUERY SELECT now(),
            0::oid AS subid, '' AS subname,
            ''::text AS worker_type,
            0::oid AS pid, NULL::integer AS leader_pid,
            0::oid AS relid, NULL::pg_lsn AS received_lsn,
            NULL::timestamp with time zone AS last_msg_send_time,
            NULL::timestamp with time zone AS last_msg_receipt_time,
            NULL::pg_lsn AS latest_end_lsn,
            NULL::timestamp with time zone AS latest_end_time
            WHERE false;
        END IF;
    ELSE
        RETURN QUERY SELECT s.ts,
            s.subid, s.subname,
            s.worker_type,
            s.pid, s.leader_pid,
            s.relid, s.received_lsn,
            s.last_msg_send_time, s.last_msg_receipt_time,
            s.latest_end_lsn, s.latest_end_time
        FROM @extschema@.powa_stat_subscription_src_tmp AS s
        WHERE s.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_stat_subscription_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_stat_subscription_stats_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT subid oid,
    OUT apply_error_count bigint,
    OUT sync_error_count bigint,
    OUT stats_reset timestamp with time zone
) RETURNS SETOF record STABLE AS $PROC$
DECLARE
    v_pg_version_num int;
BEGIN
    IF (_srvid = 0) THEN
        v_pg_version_num := current_setting('server_version_num')::int;

        -- pg15+, the view is introduced
        IF v_pg_version_num >= 150000 THEN
            RETURN QUERY SELECT now(),
            s.subid,
            s.apply_error_count, s.sync_error_count,
            s.stats_reset
            FROM pg_catalog.pg_stat_subscription_stats AS s;
        ELSE -- return an empty dataset for pg9.6- servers
            RETURN QUERY SELECT now(),
            0::oid AS subid,
            0::bigint AS apply_error_count, 0::bigint AS sync_error_count,
            NULL::timestamp with time zone AS stats_reset
            WHERE false;
        END IF;
    ELSE
        RETURN QUERY SELECT s.ts,
            s.subid,
            s.apply_error_count, s.sync_error_count,
            s.stats_reset
        FROM @extschema@.powa_stat_subscription_stats_src_tmp AS s
        WHERE s.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql; /* end of powa_stat_subscription_stats_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_stat_wal_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT wal_records bigint,
    OUT wal_fpi bigint,
    OUT wal_bytes numeric,
    OUT wal_buffers_full bigint,
    OUT wal_write bigint,
    OUT wal_sync bigint,
    OUT wal_write_time double precision,
    OUT wal_sync_time double precision,
    OUT stats_reset timestamp with time zone
) RETURNS SETOF record STABLE AS $PROC$
BEGIN
    IF (_srvid = 0) THEN
        -- pg14+, the view is introduced
        IF current_setting('server_version_num')::int >= 140000 THEN
            RETURN QUERY SELECT now(),
            s.wal_records, s.wal_fpi, s.wal_bytes,
            s.wal_buffers_full,
            s.wal_write, s.wal_sync,
            s.wal_write_time, s.wal_sync_time,
            s.stats_reset
            FROM pg_catalog.pg_stat_wal AS s;
        ELSE -- return an empty dataset for pg15- servers
            RETURN QUERY SELECT now(),
            0::bigint AS wal_records, 0::bigint AS wal_fpi,
            0::numeric AS wal_bytes,
            0::bigint AS wal_buffers_full,
            0::bigint AS wal_write, 0::bigint AS wal_sync,
            0::double precision AS wal_write_time,
            0::double precision AS wal_sync_time,
            NULL::timestamp with time zone AS stats_reset
            WHERE false;
        END IF;
    ELSE
        RETURN QUERY SELECT s.ts,
            s.wal_records, s.wal_fpi, s.wal_bytes,
            s.wal_buffers_full,
            s.wal_write, s.wal_sync,
            s.wal_write_time, s.wal_sync_time,
            s.stats_reset
        FROM @extschema@.powa_stat_wal_src_tmp AS s
        WHERE s.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_stat_wal_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_stat_wal_receiver_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT slot_name text,
    OUT sender_host text,
    OUT sender_port integer,
    OUT pid integer,
    OUT status text,
    OUT receive_start_lsn pg_lsn,
    OUT receive_start_tli integer,
    OUT last_received_lsn pg_lsn,
    OUT written_lsn pg_lsn,
    OUT flushed_lsn pg_lsn,
    OUT received_tli integer,
    OUT last_msg_send_time timestamp with time zone,
    OUT last_msg_receipt_time timestamp with time zone,
    OUT latest_end_lsn pg_lsn,
    OUT latest_end_time timestamp with time zone,
    OUT conninfo text
) RETURNS SETOF record STABLE AS $PROC$
DECLARE
    v_pg_version_num int;
    v_current_lsn pg_lsn;
BEGIN
    IF (_srvid = 0) THEN
        v_pg_version_num := current_setting('server_version_num')::int;

         -- return an empty dataset for pg9.5- servers or non-standby
        IF (NOT pg_is_in_recovery()
            OR v_pg_version_num < 90600
        ) THEN
            RETURN QUERY SELECT now(),
            ''::text AS slot_name,
            ''::text AS sender_host, 0::integer AS sender_port,
            0::integer pid, ''::text AS status,
            NULL::pg_lsn AS receive_start_lsn, 0::integer AS receive_start_tli,
            NULL::pg_lsn AS last_received_lsn,
            NULL::pg_lsn AS written_lsn, NULL::pg_lsn AS flushed_lsn,
            0::integer AS received_tli,
            NULL::timestamp with time zone AS last_msg_send_time,
            NULL::timestamp with time zone AS last_msg_receipt_time,
            NULL::pg_lsn AS latest_end_lsn,
            NULL::timestamp with time zone AS latest_end_time,
            ''::text AS conninfo
            WHERE false;
        END IF;

        IF v_pg_version_num < 100000 THEN
            v_current_lsn := pg_last_xlog_receive_location();
        ELSE
            v_current_lsn := pg_last_wal_receive_lsn();
        END IF;

        -- pg13+, received_lsn split in written_lsn and flushed_lsn
        IF v_pg_version_num >= 130000 THEN
            RETURN QUERY SELECT now(),
            s.slot_name,
            s.sender_host, s.sender_port,
            s.pid, s.status,
            s.receive_start_lsn, s.receive_start_tli,
            v_current_lsn,
            s.written_lsn, s.flushed_lsn,
            s.received_tli,
            s.last_msg_send_time,
            s.last_msg_receipt_time,
            s.latest_end_lsn,
            s.latest_end_time,
            s.conninfo
            FROM pg_catalog.pg_stat_wal_receiver AS s;
        -- pg11+, sender_host and sender_port added
        ELSIF v_pg_version_num >= 110000 THEN
            RETURN QUERY SELECT now(),
            s.slot_name,
            s.sender_host, s.sender_port,
            s.pid, s.status,
            s.receive_start_lsn, s.receive_start_tli,
            v_current_lsn,
            NULL::pg_lsn AS written_lsn, s.received_lsn AS flushed_lsn,
            s.received_tli,
            s.last_msg_send_time,
            s.last_msg_receipt_time,
            s.latest_end_lsn,
            s.latest_end_time,
            s.conninfo
            FROM pg_catalog.pg_stat_wal_receiver AS s;
        -- pg9.6+, the view is introduced
        ELSIF v_pg_version_num >= 90600 THEN
            RETURN QUERY SELECT now(),
            s.slot_name,
            NULL::text AS sender_host, NULL::integer AS sender_port,
            s.pid, s.status,
            s.receive_start_lsn, s.receive_start_tli,
            v_current_lsn,
            NULL::pg_lsn AS written_lsn, s.received_lsn AS flushed_lsn,
            s.received_tli,
            s.last_msg_send_time,
            s.last_msg_receipt_time,
            s.latest_end_lsn,
            s.latest_end_time,
            s.conninfo
            FROM pg_catalog.pg_stat_wal_receiver AS s;
        ELSE
            -- already handled above
            RAISE EXCEPTION 'bug in powa_stat_wal_receiver_src_tmp';
        END IF;
    ELSE
        RETURN QUERY SELECT s.ts,
            s.slot_name,
            s.sender_host, s.sender_port,
            s.pid, s.status,
            s.receive_start_lsn, s.receive_start_tli,
            s.last_received_lsn,
            s.written_lsn, s.flushed_lsn,
            s.received_tli,
            s.last_msg_send_time,
            s.last_msg_receipt_time,
            s.latest_end_lsn,
            s.latest_end_time,
            s.conninfo
        FROM @extschema@.powa_stat_wal_receiver_src_tmp AS s
        WHERE s.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql; /* end of powa_stat_wal_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_catalog_database_src(IN _srvid integer,
    OUT oid oid,
    OUT datname text
) RETURNS SETOF record STABLE AS $PROC$
BEGIN
    IF (_srvid = 0) THEN
        RETURN QUERY SELECT
            d.oid, d.datname::text
        FROM pg_catalog.pg_database AS d;
    ELSE
        RETURN QUERY SELECT
            d.oid, d.datname
        FROM @extschema@.powa_catalog_database_src_tmp AS d
        WHERE d.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_catalog_database_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_catalog_role_src(IN _srvid integer,
    OUT oid oid,
    OUT rolname text, OUT rolsuper boolean, OUT rolinherit boolean,
    OUT rolcreaterole boolean, OUT rolcreatedb boolean, OUT rolcanlogin
    boolean, OUT rolreplication boolean, OUT rolbypassrls boolean
) RETURNS SETOF record STABLE AS $PROC$
BEGIN
    IF (_srvid = 0) THEN
        IF current_setting('server_version_num')::int < 90500 THEN
            RETURN QUERY SELECT
                r.oid, r.rolname::text AS rolname, r.rolsuper, r.rolinherit,
                r.rolcreaterole, r.rolcreatedb, r.rolcanlogin,
                r.rolreplication, false AS rolbypassrls
            FROM pg_catalog.pg_roles AS r;
        ELSE
            RETURN QUERY SELECT
                r.oid, r.rolname::text AS rolname, r.rolsuper, r.rolinherit,
                r.rolcreaterole, r.rolcreatedb, r.rolcanlogin,
                r.rolreplication, r.rolbypassrls
            FROM pg_catalog.pg_roles AS r;
        END IF;
    ELSE
        RETURN QUERY SELECT
            r.oid, r.rolname, r.rolsuper, r.rolinherit,
            r.rolcreaterole, r.rolcreatedb, r.rolcanlogin,
            r.rolreplication, r.rolbypassrls
        FROM @extschema@.powa_catalog_role_src_tmp AS r
        WHERE r.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql; /* end of powa_catalog_role_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_catalog_database_snapshot(_srvid integer) RETURNS void AS $PROC$
DECLARE
    result boolean;
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_catalog_database_snapshot', _srvid);
    v_rowcount    bigint;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    WITH src AS (
        SELECT * FROM @extschema@.powa_catalog_database_src(_srvid)
    ),
    remove_deleted AS (
        DELETE FROM @extschema@.powa_catalog_databases AS d
        WHERE d.srvid = _srvid
        AND d.oid NOT IN (
            SELECT oid
            FROM src
        )
    ),
    add_new AS (
        INSERT INTO @extschema@.powa_catalog_databases (srvid, oid, datname)
        SELECT _srvid, src.oid, src.datname
        FROM src
        WHERE src.oid NOT IN (
            SELECT oid
            FROM @extschema@.powa_catalog_databases AS d
            WHERE d.srvid = _srvid
        )
    )
    UPDATE @extschema@.powa_catalog_databases
    SET datname = src.datname
    FROM src
    WHERE powa_catalog_databases.srvid = _srvid
    AND powa_catalog_databases.oid = src.oid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s - rowcount: %s',
            v_funcname, v_rowcount));

    IF (_srvid != 0) THEN
        DELETE FROM @extschema@.powa_catalog_database_src_tmp WHERE srvid = _srvid;
    END IF;

    result := true;
END;
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_catalog_database_snapshot */

CREATE OR REPLACE FUNCTION @extschema@.powa_catalog_role_snapshot(_srvid integer) RETURNS void AS $PROC$
DECLARE
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_catalog_role_snapshot', _srvid);
    v_rowcount    bigint;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    WITH src AS (
        SELECT * FROM @extschema@.powa_catalog_role_src(_srvid)
    ),
    remove_deleted AS (
        DELETE FROM @extschema@.powa_catalog_roles AS r
        WHERE r.srvid = _srvid
        AND r.oid NOT IN (
            SELECT oid
            FROM src
        )
    ),
    add_new AS (
        INSERT INTO @extschema@.powa_catalog_roles (srvid, oid, rolname,
            rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin,
            rolreplication, rolbypassrls)
        SELECT _srvid, src.oid, src.rolname, src.rolsuper, src.rolinherit,
            src.rolcreaterole, src.rolcreatedb, src.rolcanlogin,
            src.rolreplication, src.rolbypassrls
        FROM src
        WHERE src.oid NOT IN (
            SELECT oid
            FROM @extschema@.powa_catalog_roles AS r
            WHERE r.srvid = _srvid
        )
    )
    UPDATE @extschema@.powa_catalog_roles
    SET rolname = src.rolname, rolsuper = src.rolsuper,
        rolinherit = src.rolinherit, rolcreaterole = src.rolcreaterole,
        rolcreatedb = src.rolcreatedb, rolcanlogin = src.rolcanlogin,
        rolreplication = src.rolreplication, rolbypassrls = src.rolbypassrls
    FROM src
    WHERE powa_catalog_roles.srvid = _srvid
    AND powa_catalog_roles.oid = src.oid
    AND (powa_catalog_roles.rolname != src.rolname
         OR powa_catalog_roles.rolsuper != src.rolsuper
         OR powa_catalog_roles.rolinherit != src.rolinherit
         OR powa_catalog_roles.rolcreaterole != src.rolcreaterole
         OR powa_catalog_roles.rolcreatedb != src.rolcreatedb
         OR powa_catalog_roles.rolcanlogin != src.rolcanlogin
         OR powa_catalog_roles.rolreplication != src.rolreplication
         OR powa_catalog_roles.rolbypassrls != src.rolbypassrls);

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s - rowcount: %s',
            v_funcname, v_rowcount));

    IF (_srvid != 0) THEN
        DELETE FROM @extschema@.powa_catalog_role_src_tmp WHERE srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_catalog_role_snapshot() */

CREATE OR REPLACE FUNCTION @extschema@.powa_catalog_generic_snapshot(_srvid integer,
    _catname text)
RETURNS void AS $PROC$
DECLARE
    v_funcname    text := format('@extschema@.%I(%s, %s)',
                                 'powa_catalog_generic_snapshot',
                                 _srvid, _catname);
    v_rowcount    bigint;
    v_prefix      text;
    v_src_tmp     text;
    v_query       text;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    -- get the table prefix and src_tmp table name
    SELECT 'powa_catalog_' || replace(_catname, 'pg_', '')
        INTO STRICT v_prefix;

    SELECT quote_ident(v_prefix || '_src_tmp') INTO STRICT v_src_tmp;

    -- bail out if there's no source data
    EXECUTE format('SELECT 1 FROM @extschema@.%s WHERE srvid = %s LIMIT 1',
        v_src_tmp, _srvid) INTO v_rowcount;

    IF v_rowcount IS NULL THEN
        RETURN;
    END IF;

    -- Remove all records for the given server.
    -- Note that only remove record for found database oid so we can handle
    -- partial per-db snapshot.  This has to be done in a different step as
    -- wCTE don't see the results of previous wCTE.
    -- If a database is removed from a remote server, all the underyling
    -- records will already be removed when cascading the delete in the
    -- powa_catalog_databases table.
    EXECUTE format('DELETE FROM @extschema@.%s
        WHERE srvid = %s
        AND dbid IN (SELECT DISTINCT dbid
            FROM @extschema@.%s
            WHERE srvid = %s
    )', v_prefix, _srvid, v_src_tmp, _srvid);

    -- Insert the new records.
    -- We also finally save the refresh time.  We only want to do it once per
    -- remote server and not once per catalog, so arbitrarily do that for the
    -- pg_class catalog only, which is done last.
    v_query := format('WITH src AS (
             DELETE FROM @extschema@.%1$s
             WHERE srvid = %3$s
             RETURNING *
        ),
        metadata AS (
            UPDATE @extschema@.powa_catalog_databases
            SET last_refresh = now()
            WHERE srvid = %3$s
            AND %4$L = ''pg_class''
            AND oid IN (SELECT DISTINCT dbid
                FROM src)
        )
        INSERT INTO @extschema@.%2$s
        SELECT *
        FROM src', v_src_tmp, v_prefix, _srvid, _catname);

    -- execute it
    EXECUTE v_query;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s - rowcount: %s',
            v_funcname, v_rowcount));
END;
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_catalog_generic_snapshot */

CREATE OR REPLACE FUNCTION @extschema@.powa_catalog_reset(_srvid integer)
RETURNS void AS $PROC$
DECLARE
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_catalog_reset',
                                 _srvid);
    v_catname     text;
    v_prefix      text;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    FOR v_catname IN SELECT catname FROM @extschema@.powa_catalogs
    LOOP
        SELECT 'powa_catalog_' || replace(v_catname, 'pg_', '')
            INTO STRICT v_prefix;

        PERFORM format('DELETE FROM @extschema@.%I WHERE srvid = %s',
            v_prefix, _srvid);
        PERFORM format('DELETE FROM @extschema@.%I WHERE srvid = %s',
            v_prefix || '_src_tmp', _srvid);
    END LOOP;
END;
$PROC$ language plpgsql; /* end of powa_catalog_reset */


CREATE OR REPLACE FUNCTION @extschema@.powa_databases_purge(_srvid integer)
RETURNS void AS $PROC$
DECLARE
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_databases_purge', _srvid);
    v_rowcount    bigint;
    v_dropped_dbid oid[];
    v_retention   interval;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    SELECT @extschema@.powa_get_server_retention(_srvid) INTO v_retention;

    -- Cleanup old dropped databases, over retention
    -- This will cascade automatically to powa_statements and other
    WITH dropped_databases AS
      ( DELETE FROM @extschema@.powa_databases
        WHERE dropped < (now() - v_retention * 1.2)
        AND srvid = _srvid
        RETURNING oid
        )
    SELECT array_agg(oid) INTO v_dropped_dbid FROM dropped_databases;

    PERFORM @extschema@.powa_log(format('%s (powa_databases) - rowcount: %s)',
           v_funcname,array_length(v_dropped_dbid,1)));
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_databases_purge */


CREATE OR REPLACE FUNCTION @extschema@.powa_statements_purge(_srvid integer)
RETURNS void AS $PROC$
DECLARE
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_statements_purge', _srvid);
    v_rowcount    bigint;
    v_retention   interval;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    SELECT @extschema@.powa_get_server_retention(_srvid) INTO v_retention;

    -- Delete obsolete data. We only bother with already coalesced data
    DELETE FROM @extschema@.powa_statements_history
    WHERE upper(coalesce_range)< (now() - v_retention)
    AND srvid = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s (powa_statements_hitory) - rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_statements_history_db
    WHERE upper(coalesce_range)< (now() - v_retention)
    AND srvid = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s (powa_statements_history_db) - rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_statements
    WHERE last_present_ts < (now() - v_retention)
    AND srvid = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s (powa_statements) - rowcount: %s',
            v_funcname, v_rowcount));
END;
$PROC$ LANGUAGE plpgsql; /* end of powa_statements_purge */

CREATE OR REPLACE FUNCTION @extschema@.powa_user_functions_purge(_srvid integer)
RETURNS void AS $PROC$
DECLARE
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_user_functions_purge', _srvid);
    v_rowcount    bigint;
    v_retention   interval;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    SELECT @extschema@.powa_get_server_retention(_srvid) INTO v_retention;

    -- Delete obsolete datas. We only bother with already coalesced data
    DELETE FROM @extschema@.powa_user_functions_history
    WHERE upper(coalesce_range)< (now() - v_retention)
    AND srvid = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s - (powa_user_functions_history) rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_user_functions_history_db
    WHERE upper(coalesce_range)< (now() - v_retention)
    AND srvid = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s - (powa_user_functions_history_db) rowcount: %s',
            v_funcname, v_rowcount));
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_user_functions_purge */

CREATE OR REPLACE FUNCTION @extschema@.powa_all_indexes_purge(_srvid integer)
RETURNS void AS $PROC$
DECLARE
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_all_indexes_purge', _srvid);
    v_rowcount    bigint;
    v_retention   interval;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    SELECT @extschema@.powa_get_server_retention(_srvid) INTO v_retention;

    -- Delete obsolete datas. We only bother with already coalesced data
    DELETE FROM @extschema@.powa_all_indexes_history
    WHERE upper(coalesce_range)< (now() - v_retention)
    AND srvid = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s - (powa_all_indexes_history) rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_all_indexes_history_db
    WHERE upper(coalesce_range)< (now() - v_retention)
    AND srvid = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s - (powa_all_indexes_history_db) rowcount: %s',
            v_funcname, v_rowcount));
END;
$PROC$ LANGUAGE plpgsql; /* end of powa_all_indexes_purge */

CREATE OR REPLACE FUNCTION @extschema@.powa_all_tables_purge(_srvid integer)
RETURNS void AS $PROC$
DECLARE
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_all_tables_purge', _srvid);
    v_rowcount    bigint;
    v_retention   interval;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    SELECT @extschema@.powa_get_server_retention(_srvid) INTO v_retention;

    -- Delete obsolete datas. We only bother with already coalesced data
    DELETE FROM @extschema@.powa_all_tables_history
    WHERE upper(coalesce_range)< (now() - v_retention)
    AND srvid = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s - (powa_all_tables_history) rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_all_tables_history_db
    WHERE upper(coalesce_range)< (now() - v_retention)
    AND srvid = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s - (powa_all_tables_history_db) rowcount: %s',
            v_funcname, v_rowcount));
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_all_tables_purge */

CREATE OR REPLACE FUNCTION @extschema@.powa_statements_aggregate(_srvid integer)
RETURNS void AS $PROC$
DECLARE
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_statements_aggregate', _srvid);
    v_rowcount    bigint;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    -- aggregate statements table
    INSERT INTO @extschema@.powa_statements_history (srvid, queryid, dbid, toplevel,
            userid, coalesce_range, records, mins_in_range, maxs_in_range)
        SELECT srvid, queryid, dbid, toplevel, userid,
            tstzrange(min((record).ts), max((record).ts),'[]'),
            array_agg(record),
            ROW(min((record).ts),
                min((record).calls),min((record).total_exec_time),
                min((record).rows),
                min((record).shared_blks_hit),min((record).shared_blks_read),
                min((record).shared_blks_dirtied),min((record).shared_blks_written),
                min((record).local_blks_hit),min((record).local_blks_read),
                min((record).local_blks_dirtied),min((record).local_blks_written),
                min((record).temp_blks_read),min((record).temp_blks_written),
                min((record).shared_blk_read_time),min((record).shared_blk_write_time),
                min((record).local_blk_read_time),min((record).local_blk_write_time),
                min((record).temp_blk_read_time),min((record).temp_blk_write_time),
                min((record).plans),min((record).total_plan_time),
                min((record).wal_records),min((record).wal_fpi),
                min((record).wal_bytes),
                min((record).jit_functions), min((record).jit_generation_time),
                min((record).jit_inlining_count), min((record).jit_inlining_time),
                min((record).jit_optimization_count), min((record).jit_optimization_time),
                min((record).jit_emission_count), min((record).jit_emission_time),
                min((record).jit_deform_count), min((record).jit_deform_time)
            )::@extschema@.powa_statements_history_record,
            ROW(max((record).ts),
                max((record).calls),max((record).total_exec_time),
                max((record).rows),
                max((record).shared_blks_hit),max((record).shared_blks_read),
                max((record).shared_blks_dirtied),max((record).shared_blks_written),
                max((record).local_blks_hit),max((record).local_blks_read),
                max((record).local_blks_dirtied),max((record).local_blks_written),
                max((record).temp_blks_read),max((record).temp_blks_written),
                max((record).shared_blk_read_time),max((record).shared_blk_write_time),
                max((record).local_blk_read_time),max((record).local_blk_write_time),
                max((record).temp_blk_read_time),max((record).temp_blk_write_time),
                max((record).plans),max((record).total_plan_time),
                max((record).wal_records),max((record).wal_fpi),
                max((record).wal_bytes),
                max((record).jit_functions), max((record).jit_generation_time),
                max((record).jit_inlining_count), max((record).jit_inlining_time),
                max((record).jit_optimization_count), max((record).jit_optimization_time),
                max((record).jit_emission_count), max((record).jit_emission_time),
                max((record).jit_deform_count), max((record).jit_deform_time)
            )::@extschema@.powa_statements_history_record
        FROM @extschema@.powa_statements_history_current
        WHERE srvid = _srvid
        GROUP BY srvid, queryid, dbid, toplevel, userid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s (powa_statements_history) - rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_statements_history_current WHERE srvid = _srvid;

    -- aggregate db table
    INSERT INTO @extschema@.powa_statements_history_db (srvid, dbid, coalesce_range,
            records, mins_in_range, maxs_in_range)
        SELECT srvid, dbid,
            tstzrange(min((record).ts), max((record).ts),'[]'),
            array_agg(record),
            ROW(min((record).ts),
                min((record).calls),min((record).total_exec_time),
                min((record).rows),
                min((record).shared_blks_hit),min((record).shared_blks_read),
                min((record).shared_blks_dirtied),min((record).shared_blks_written),
                min((record).local_blks_hit),min((record).local_blks_read),
                min((record).local_blks_dirtied),min((record).local_blks_written),
                min((record).temp_blks_read),min((record).temp_blks_written),
                min((record).shared_blk_read_time),min((record).shared_blk_write_time),
                min((record).local_blk_read_time),min((record).local_blk_write_time),
                min((record).temp_blk_read_time),min((record).temp_blk_write_time),
                min((record).plans),min((record).total_plan_time),
                min((record).wal_records),min((record).wal_fpi),
                min((record).wal_bytes),
                min((record).jit_functions), min((record).jit_generation_time),
                min((record).jit_inlining_count), min((record).jit_inlining_time),
                min((record).jit_optimization_count), min((record).jit_optimization_time),
                min((record).jit_emission_count), min((record).jit_emission_time),
                min((record).jit_deform_count), min((record).jit_deform_time)
            )::@extschema@.powa_statements_history_record,
            ROW(max((record).ts),
                max((record).calls),max((record).total_exec_time),
                max((record).rows),
                max((record).shared_blks_hit),max((record).shared_blks_read),
                max((record).shared_blks_dirtied),max((record).shared_blks_written),
                max((record).local_blks_hit),max((record).local_blks_read),
                max((record).local_blks_dirtied),max((record).local_blks_written),
                max((record).temp_blks_read),max((record).temp_blks_written),
                max((record).shared_blk_read_time),max((record).shared_blk_write_time),
                max((record).local_blk_read_time),max((record).local_blk_write_time),
                max((record).temp_blk_read_time),max((record).temp_blk_write_time),
                max((record).plans),max((record).total_plan_time),
                max((record).wal_records),max((record).wal_fpi),
                max((record).wal_bytes),
                max((record).jit_functions), max((record).jit_generation_time),
                max((record).jit_inlining_count), max((record).jit_inlining_time),
                max((record).jit_optimization_count), max((record).jit_optimization_time),
                max((record).jit_emission_count), max((record).jit_emission_time),
                max((record).jit_deform_count), max((record).jit_deform_time)
            )::@extschema@.powa_statements_history_record
        FROM @extschema@.powa_statements_history_current_db
        WHERE srvid = _srvid
        GROUP BY srvid, dbid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s (powa_statements_history_db) - rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_statements_history_current_db WHERE srvid = _srvid;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_statements_aggregate */

CREATE OR REPLACE FUNCTION @extschema@.powa_user_functions_aggregate(_srvid integer)
RETURNS void AS $PROC$
DECLARE
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_user_functions_aggregate', _srvid);
    v_rowcount    bigint;
BEGIN
    PERFORM @extschema@.powa_log('running powa_user_functions_aggregate(' || _srvid ||')');

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    -- aggregate user_functions table
    INSERT INTO @extschema@.powa_user_functions_history
        (srvid, dbid, funcid, coalesce_range, records,
                mins_in_range, maxs_in_range)
        SELECT srvid, dbid, funcid,
            tstzrange(min((record).ts), max((record).ts),'[]'),
            array_agg(record),
            ROW(min((record).ts), min((record).calls),min((record).total_time),
                min((record).self_time))::@extschema@.powa_user_functions_history_record,
            ROW(max((record).ts), max((record).calls),max((record).total_time),
                max((record).self_time))::@extschema@.powa_user_functions_history_record
        FROM @extschema@.powa_user_functions_history_current
        WHERE srvid = _srvid
        GROUP BY srvid, dbid, funcid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s - (powa_user_functions_history_current) rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_user_functions_history_current WHERE srvid = _srvid;

    -- aggregate user_functions_db table
    INSERT INTO @extschema@.powa_user_functions_history_db
        (srvid, dbid, coalesce_range, records, mins_in_range, maxs_in_range)
        SELECT srvid, dbid,
            tstzrange(min((record).ts), max((record).ts),'[]'),
            array_agg(record),
            ROW(min((record).ts), min((record).calls),min((record).total_time),
                min((record).self_time))::@extschema@.powa_user_functions_history_record,
            ROW(max((record).ts), max((record).calls),max((record).total_time),
                max((record).self_time))::@extschema@.powa_user_functions_history_record
        FROM @extschema@.powa_user_functions_history_current
        WHERE srvid = _srvid
        GROUP BY srvid, dbid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s - (powa_user_functions_history_current_db) rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_user_functions_history_current_db WHERE srvid = _srvid;
 END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_user_functions_aggregate */

CREATE OR REPLACE FUNCTION @extschema@.powa_all_indexes_aggregate(_srvid integer)
RETURNS void AS $PROC$
DECLARE
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_all_indexes_aggregate', _srvid);
    v_rowcount    bigint;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    -- aggregate all_indexes table
    INSERT INTO @extschema@.powa_all_indexes_history
        (srvid, dbid, relid, indexrelid, coalesce_range, records,
                mins_in_range, maxs_in_range)
        SELECT srvid, dbid, relid, indexrelid,
            tstzrange(min((record).ts), max((record).ts),'[]'),
            array_agg(record),
            ROW(min((record).ts),
                min((record).idx_size),
                min((record).idx_scan), min((record).last_idx_scan),
                min((record).idx_tup_read), min((record).idx_tup_fetch),
                min((record).idx_blks_read), min((record).idx_blks_hit)
            )::@extschema@.powa_all_indexes_history_record,
            ROW(max((record).ts),
                max((record).idx_size),
                max((record).idx_scan), max((record).last_idx_scan),
                max((record).idx_tup_read), max((record).idx_tup_fetch),
                max((record).idx_blks_read), max((record).idx_blks_hit)
            )::@extschema@.powa_all_indexes_history_record
        FROM @extschema@.powa_all_indexes_history_current
        WHERE srvid = _srvid
        GROUP BY srvid, dbid, relid, indexrelid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s - (powa_all_indexes_history_current) rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_all_indexes_history_current WHERE srvid = _srvid;

    -- aggregate all_indexes_db table
    INSERT INTO @extschema@.powa_all_indexes_history_db
        (srvid, dbid, coalesce_range, records, mins_in_range, maxs_in_range)
        SELECT srvid, dbid,
            tstzrange(min((record).ts), max((record).ts),'[]'),
            array_agg(record),
            ROW(min((record).ts),
                min((record).idx_size),
                min((record).idx_scan),
                min((record).idx_tup_read), min((record).idx_tup_fetch),
                min((record).idx_blks_read), min((record).idx_blks_hit)
            )::@extschema@.powa_all_indexes_history_db_record,
            ROW(max((record).ts),
                max((record).idx_size),
                max((record).idx_scan),
                max((record).idx_tup_read), max((record).idx_tup_fetch),
                max((record).idx_blks_read), max((record).idx_blks_hit)
            )::@extschema@.powa_all_indexes_history_db_record
        FROM @extschema@.powa_all_indexes_history_current_db
        WHERE srvid = _srvid
        GROUP BY srvid, dbid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s (powa_all_indexes_history_db) - rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_all_indexes_history_current_db WHERE srvid = _srvid;
 END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_all_indexes_aggregate */

CREATE OR REPLACE FUNCTION @extschema@.powa_all_tables_aggregate(_srvid integer)
RETURNS void AS $PROC$
DECLARE
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_all_tables_aggregate', _srvid);
    v_rowcount    bigint;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    -- aggregate all_tables table
    INSERT INTO @extschema@.powa_all_tables_history
        (srvid, dbid, relid, coalesce_range, records,
                mins_in_range, maxs_in_range)
        SELECT srvid, dbid, relid,
            tstzrange(min((record).ts), max((record).ts),'[]'),
            array_agg(record),
            ROW(min((record).ts),
                min((record).tbl_size),
                min((record).seq_scan), min((record).last_seq_scan),
                min((record).seq_tup_read), min((record).idx_scan),
                min((record).last_idx_scan), min((record).n_tup_ins),
                min((record).n_tup_upd), min((record).n_tup_del),
                min((record).n_tup_hot_upd), min((record).n_tup_newpage_upd),
                min((record).n_liv_tup), min((record).n_dead_tup),
                min((record).n_mod_since_analyze),
                min((record).n_ins_since_vacuum), min((record).last_vacuum),
                min((record).last_autovacuum), min((record).last_analyze),
                min((record).last_autoanalyze), min((record).vacuum_count),
                min((record).autovacuum_count), min((record).analyze_count),
                min((record).autoanalyze_count), min((record).heap_blks_read),
                min((record).heap_blks_hit), min((record).idx_blks_read),
                min((record).idx_blks_hit), min((record).toast_blks_read),
                min((record).toast_blks_hit), min((record).tidx_blks_read),
                min((record).tidx_blks_hit)
            )::@extschema@.powa_all_tables_history_record,
            ROW(max((record).ts),
                max((record).tbl_size),
                max((record).seq_scan), max((record).last_seq_scan),
                max((record).seq_tup_read), max((record).idx_scan),
                max((record).last_idx_scan), max((record).n_tup_ins),
                max((record).n_tup_upd), max((record).n_tup_del),
                max((record).n_tup_hot_upd), max((record).n_tup_newpage_upd),
                max((record).n_liv_tup), max((record).n_dead_tup),
                max((record).n_mod_since_analyze),
                max((record).n_ins_since_vacuum), max((record).last_vacuum),
                max((record).last_autovacuum), max((record).last_analyze),
                max((record).last_autoanalyze), max((record).vacuum_count),
                max((record).autovacuum_count), max((record).analyze_count),
                max((record).autoanalyze_count), max((record).heap_blks_read),
                max((record).heap_blks_hit), max((record).idx_blks_read),
                max((record).idx_blks_hit), max((record).toast_blks_read),
                max((record).toast_blks_hit), max((record).tidx_blks_read),
                max((record).tidx_blks_hit)
            )::@extschema@.powa_all_tables_history_record
        FROM @extschema@.powa_all_tables_history_current
        WHERE srvid = _srvid
        GROUP BY srvid, dbid, relid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s - (powa_all_tables_history_current) rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_all_tables_history_current WHERE srvid = _srvid;

    -- aggregate all_tables_db table
    INSERT INTO @extschema@.powa_all_tables_history_db
        (srvid, dbid, coalesce_range, records, mins_in_range, maxs_in_range)
        SELECT srvid, dbid,
            tstzrange(min((record).ts), max((record).ts),'[]'),
            array_agg(record),
            ROW(min((record).ts),
                min((record).tbl_size),
                min((record).seq_scan), min((record).seq_tup_read),
                min((record).idx_scan), min((record).n_tup_ins),
                min((record).n_tup_upd), min((record).n_tup_del),
                min((record).n_tup_hot_upd), min((record).n_tup_newpage_upd),
                min((record).n_liv_tup), min((record).n_dead_tup),
                min((record).n_mod_since_analyze),
                min((record).n_ins_since_vacuum), min((record).vacuum_count),
                min((record).autovacuum_count), min((record).analyze_count),
                min((record).autoanalyze_count), min((record).heap_blks_read),
                min((record).heap_blks_hit), min((record).idx_blks_read),
                min((record).idx_blks_hit), min((record).toast_blks_read),
                min((record).toast_blks_hit), min((record).tidx_blks_read),
                min((record).tidx_blks_hit)
            )::@extschema@.powa_all_tables_history_db_record,
            ROW(max((record).ts),
                max((record).tbl_size),
                max((record).seq_scan), max((record).seq_tup_read),
                max((record).idx_scan), max((record).n_tup_ins),
                max((record).n_tup_upd), max((record).n_tup_del),
                max((record).n_tup_hot_upd), max((record).n_tup_newpage_upd),
                max((record).n_liv_tup), max((record).n_dead_tup),
                max((record).n_mod_since_analyze),
                max((record).n_ins_since_vacuum), max((record).vacuum_count),
                max((record).autovacuum_count), max((record).analyze_count),
                max((record).autoanalyze_count), max((record).heap_blks_read),
                max((record).heap_blks_hit), max((record).idx_blks_read),
                max((record).idx_blks_hit), max((record).toast_blks_read),
                max((record).toast_blks_hit), max((record).tidx_blks_read),
                max((record).tidx_blks_hit)
            )::@extschema@.powa_all_tables_history_db_record
        FROM @extschema@.powa_all_tables_history_current_db
        WHERE srvid = _srvid
        GROUP BY srvid, dbid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s (powa_all_tables_history_db) - rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_all_tables_history_current_db WHERE srvid = _srvid;
 END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_all_tables_aggregate */

CREATE OR REPLACE FUNCTION @extschema@.powa_reset(_srvid integer)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
DECLARE
  r         record;
  v_state   text;
  v_msg     text;
  v_detail  text;
  v_hint    text;
  v_context text;
BEGIN
    -- Find reset function for every supported datasource, including pgss
    FOR r IN SELECT CASE external
                WHEN true THEN quote_ident(nsp.nspname)
                ELSE '@extschema@'
            END AS schema, function_name AS funcname
            FROM @extschema@.powa_all_functions AS pf
            LEFT JOIN pg_extension AS ext ON pf.kind = 'extension'
               AND ext.extname = pf.name
            LEFT JOIN pg_namespace AS nsp ON nsp.oid = ext.extnamespace
            WHERE operation='reset'
            AND srvid = _srvid
            ORDER BY priority, name LOOP
      -- Call all of them, for the current srvid
      BEGIN
          EXECUTE format('SELECT %s.%I(%s)', r.schema, r.funcname, _srvid);
      EXCEPTION
        WHEN OTHERS THEN
          GET STACKED DIAGNOSTICS
              v_state   = RETURNED_SQLSTATE,
              v_msg     = MESSAGE_TEXT,
              v_detail  = PG_EXCEPTION_DETAIL,
              v_hint    = PG_EXCEPTION_HINT,
              v_context = PG_EXCEPTION_CONTEXT;
          RAISE warning 'powa_reset(): function "%.%(%)" failed:
              state  : %
              message: %
              detail : %
              hint   : %
              context: %',
              r.schema, quote_ident(r.funcname), _srvid, v_state, v_msg,
              v_detail, v_hint, v_context;

      END;
    END LOOP;

    -- And reset all catalogs
    BEGIN
      PERFORM @extschema@.powa_catalog_reset(_srvid);
    EXCEPTION
      WHEN OTHERS THEN
        GET STACKED DIAGNOSTICS
            v_state   = RETURNED_SQLSTATE,
            v_msg     = MESSAGE_TEXT,
            v_detail  = PG_EXCEPTION_DETAIL,
            v_hint    = PG_EXCEPTION_HINT,
            v_context = PG_EXCEPTION_CONTEXT;
        RAISE warning 'powa_reset(): function "@extschema@.powa_catalog_reset(%)" failed:
            state  : %
            message: %
            detail : %
            hint   : %
            context: %',
            _srvid, v_state, v_msg, v_detail, v_hint, v_context;
    END;

    RETURN true;
END;
$function$
SET search_path = pg_catalog; /* end of powa_reset */

CREATE OR REPLACE FUNCTION @extschema@.powa_statements_reset(_srvid integer)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
BEGIN
    PERFORM @extschema@.powa_log('Resetting powa_statements_history(' || _srvid || ')');
    DELETE FROM @extschema@.powa_statements_history WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_statements_history_current(' || _srvid || ')');
    DELETE FROM @extschema@.powa_statements_history_current WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_statements_history_db(' || _srvid || ')');
    DELETE FROM @extschema@.powa_statements_history_db WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_statements_history_current_db(' || _srvid || ')');
    DELETE FROM @extschema@.powa_statements_history_current_db WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_statements_src_tmp(' || _srvid || ')');
    DELETE FROM @extschema@.powa_statements_src_tmp WHERE srvid = _srvid;

    -- if 3rd part datasource has FK on it, throw everything away
    DELETE FROM @extschema@.powa_statements WHERE srvid = _srvid;
    PERFORM @extschema@.powa_log('Resetting powa_statements(' || _srvid || ')');

    RETURN true;
END;
$function$
SET search_path = pg_catalog; /* end of powa_statements_reset */

CREATE OR REPLACE FUNCTION @extschema@.powa_user_functions_reset(_srvid integer)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
BEGIN
    PERFORM @extschema@.powa_log('Resetting powa_user_functions_history(' || _srvid || ')');
    DELETE FROM @extschema@.powa_user_functions_history WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_user_functions_history_db(' || _srvid || ')');
    DELETE FROM @extschema@.powa_user_functions_history_db WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_user_functions_history_current(' || _srvid || ')');
    DELETE FROM @extschema@.powa_user_functions_history_current WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_user_functions_history_current_db(' || _srvid || ')');
    DELETE FROM @extschema@.powa_user_functions_history_current_db WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_user_functions_src_tmp(' || _srvid || ')');
    DELETE FROM @extschema@.powa_user_functions_src_tmp WHERE srvid = _srvid;

    RETURN true;
END;
$function$; /* end of powa_user_functions_reset */

CREATE OR REPLACE FUNCTION @extschema@.powa_all_indexes_reset(_srvid integer)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
BEGIN
    PERFORM @extschema@.powa_log('Resetting powa_all_indexes_history(' || _srvid || ')');
    DELETE FROM @extschema@.powa_all_indexes_history WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_all_indexes_history_db(' || _srvid || ')');
    DELETE FROM @extschema@.powa_all_indexes_history_db WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_all_indexes_history_current(' || _srvid || ')');
    DELETE FROM @extschema@.powa_all_indexes_history_current WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_all_indexes_history_current_db(' || _srvid || ')');
    DELETE FROM @extschema@.powa_all_indexes_history_current_db WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_all_indexes_src_tmp(' || _srvid || ')');
    DELETE FROM @extschema@.powa_all_indexes_src_tmp WHERE srvid = _srvid;

    RETURN true;
END;
$function$
SET search_path = pg_catalog; /* end of powa_all_indexes_reset */

CREATE OR REPLACE FUNCTION @extschema@.powa_all_tables_reset(_srvid integer)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
BEGIN
    PERFORM @extschema@.powa_log('Resetting powa_all_tables_history(' || _srvid || ')');
    DELETE FROM @extschema@.powa_all_tables_history WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_all_tables_history_db(' || _srvid || ')');
    DELETE FROM @extschema@.powa_all_tables_history_db WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_all_tables_history_current(' || _srvid || ')');
    DELETE FROM @extschema@.powa_all_tables_history_current WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_all_tables_history_current_db(' || _srvid || ')');
    DELETE FROM @extschema@.powa_all_tables_history_current_db WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_all_tables_src_tmp(' || _srvid || ')');
    DELETE FROM @extschema@.powa_all_tables_src_tmp WHERE srvid = _srvid;

    RETURN true;
END;
$function$
SET search_path = pg_catalog; /* end of powa_all_tables_reset */

CREATE OR REPLACE FUNCTION @extschema@.powa_catalog_database_reset(_srvid integer)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
BEGIN
    PERFORM @extschema@.powa_log('Resetting powa_catalog_databases(' || _srvid || ')');
    DELETE FROM @extschema@.powa_catalog_databases WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_catalog_database_src_tmp(' || _srvid || ')');
    DELETE FROM @extschema@.powa_catalog_database_src_tmp WHERE srvid = _srvid;

    RETURN true;
END;
$function$
SET search_path = pg_catalog; /* end of powa_catalog_database_reset */

CREATE OR REPLACE FUNCTION @extschema@.powa_catalog_role_reset(_srvid integer)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
BEGIN
    PERFORM @extschema@.powa_log('Resetting powa_catalog_roles(' || _srvid || ')');
    DELETE FROM @extschema@.powa_catalog_roles WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('Resetting powa_catalog_role_src_tmp(' || _srvid || ')');
    DELETE FROM @extschema@.powa_catalog_role_src_tmp WHERE srvid = _srvid;

    RETURN true;
END;
$function$; /* end of powa_catalog_role_reset */

/* pg_stat_kcache integration - part 2 */

CREATE OR REPLACE FUNCTION @extschema@.powa_kcache_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT queryid bigint, OUT top bool, OUT userid oid, OUT dbid oid,
    OUT plan_reads bigint, OUT plan_writes bigint,
    OUT plan_user_time double precision, OUT plan_system_time double precision,
    OUT plan_minflts bigint, OUT plan_majflts bigint,
    OUT plan_nswaps bigint,
    OUT plan_msgsnds bigint, OUT plan_msgrcvs bigint,
    OUT plan_nsignals bigint,
    OUT plan_nvcsws bigint, OUT plan_nivcsws bigint,
    OUT exec_reads bigint, OUT exec_writes bigint,
    OUT exec_user_time double precision, OUT exec_system_time double precision,
    OUT exec_minflts bigint, OUT exec_majflts bigint,
    OUT exec_nswaps bigint,
    OUT exec_msgsnds bigint, OUT exec_msgrcvs bigint,
    OUT exec_nsignals bigint,
    OUT exec_nvcsws bigint, OUT exec_nivcsws bigint
) RETURNS SETOF record STABLE AS $PROC$
DECLARE
  is_v2_2 bool;
  v_nsp text;
BEGIN
    IF (_srvid = 0) THEN
        SELECT (
            (regexp_split_to_array(extversion, E'\\.')::int[])[1] >= 2 AND
            (regexp_split_to_array(extversion, E'\\.')::int[])[2] >= 2
        ), nspname INTO is_v2_2, v_nsp
          FROM pg_catalog.pg_extension e
          JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace
          WHERE extname = 'pg_stat_kcache';

        IF (is_v2_2 IS NOT DISTINCT FROM 'true'::bool) THEN
            RETURN QUERY EXECUTE format($$SELECT now(),
                k.queryid, k.top, k.userid, k.dbid,
                k.plan_reads, k.plan_writes,
                k.plan_user_time, k.plan_system_time,
                k.plan_minflts, k.plan_majflts, k.plan_nswaps,
                k.plan_msgsnds, k.plan_msgrcvs, k.plan_nsignals,
                k.plan_nvcsws, k.plan_nivcsws,
                k.exec_reads, k.exec_writes,
                k.exec_user_time, k.exec_system_time,
                k.exec_minflts, k.exec_majflts, k.exec_nswaps,
                k.exec_msgsnds, k.exec_msgrcvs, k.exec_nsignals,
                k.exec_nvcsws, k.exec_nivcsws
            FROM %I.pg_stat_kcache() k
            JOIN pg_catalog.pg_roles r ON r.oid = k.userid
            WHERE NOT (r.rolname = ANY (string_to_array(
                        @extschema@.powa_get_guc('powa.ignored_users', ''),
                        ',')))
            AND k.dbid NOT IN (
                SELECT oid FROM @extschema@.powa_databases
                WHERE dropped IS NOT NULL)
            $$, v_nsp);
        ELSE
            RETURN QUERY EXECUTE format($$SELECT now(),
                k.queryid, 'true'::bool as top, k.userid, k.dbid,
                NULL::bigint AS plan_reads, NULL::bigint AS plan_writes,
                NULL::double precision AS plan_user_time,
                NULL::double precision AS plan_system_time,
                NULL::bigint AS plan_minflts, NULL::bigint AS plan_majflts,
                NULL::bigint AS plan_nswaps,
                NULL::bigint AS plan_msgsnds, NULL::bigint AS plan_msgrcvs,
                NULL::bigint AS plan_nsignals,
                NULL::bigint AS plan_nvcsws, NULL::bigint AS plan_nivcsws,
                k.reads AS exec_reads, k.writes AS exec_writes,
                k.user_time AS exec_user_time, k.system_time AS exec_system_time,
                k.minflts AS exec_minflts, k.majflts AS exec_majflts,
                k.nswaps AS exec_nswaps,
                k.msgsnds AS exec_msgsnds, k.msgrcvs AS exec_msgrcvs,
                k.nsignals AS exec_nsignals,
                k.nvcsws AS exec_nvcsws, k.nivcsws AS exec_nivcsws
            FROM %I.pg_stat_kcache() k
            JOIN pg_catalog.pg_roles r ON r.oid = k.userid
            WHERE NOT (r.rolname = ANY (string_to_array(
                        @extschema@.powa_get_guc('powa.ignored_users', ''),
                        ',')))
            AND k.dbid NOT IN (
                SELECT oid FROM @extschema@.powa_databases
                WHERE dropped IS NOT NULL)
            $$, v_nsp);
        END IF;
    ELSE
        RETURN QUERY SELECT k.ts,
            k.queryid, k.top, k.userid, k.dbid,
            k.plan_reads, k.plan_writes,
            k.plan_user_time, k.plan_system_time,
            k.plan_minflts, k.plan_majflts, k.plan_nswaps,
            k.plan_msgsnds, k.plan_msgrcvs, k.plan_nsignals,
            k.plan_nvcsws, k.plan_nivcsws,
            k.exec_reads, k.exec_writes,
            k.exec_user_time, k.exec_system_time,
            k.exec_minflts, k.exec_majflts, k.exec_nswaps,
            k.exec_msgsnds, k.exec_msgrcvs, k.exec_nsignals,
            k.exec_nvcsws, k.exec_nivcsws
        FROM @extschema@.powa_kcache_src_tmp k
        WHERE k.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_kcache_src */

/*
 * powa_kcache snapshot collection.
 */
CREATE OR REPLACE FUNCTION @extschema@.powa_kcache_snapshot(_srvid integer) RETURNS void as $PROC$
DECLARE
  result bool;
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_kcache_snapshot', _srvid);
    v_rowcount    bigint;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    WITH capture AS (
        SELECT *
        FROM @extschema@.powa_kcache_src(_srvid)
    ),

    by_query AS (
        INSERT INTO @extschema@.powa_kcache_metrics_current (srvid, queryid, top, dbid, userid, metrics)
            SELECT _srvid, queryid, top, dbid, userid,
              (ts,
               plan_reads, plan_writes, plan_user_time, plan_system_time,
               plan_minflts, plan_majflts, plan_nswaps,
               plan_msgsnds, plan_msgrcvs, plan_nsignals,
               plan_nvcsws, plan_nivcsws,
               exec_reads, exec_writes, exec_user_time, exec_system_time,
               exec_minflts, exec_majflts, exec_nswaps,
               exec_msgsnds, exec_msgrcvs, exec_nsignals,
               exec_nvcsws, exec_nivcsws
        )::@extschema@.powa_kcache_history_record
            FROM capture
    ),

    by_database AS (
        INSERT INTO @extschema@.powa_kcache_metrics_current_db (srvid, top, dbid, metrics)
            SELECT _srvid AS srvid, top, dbid,
              (ts,
               sum(plan_reads), sum(plan_writes),
               sum(plan_user_time), sum(plan_system_time),
               sum(plan_minflts), sum(plan_majflts), sum(plan_nswaps),
               sum(plan_msgsnds), sum(plan_msgrcvs), sum(plan_nsignals),
               sum(plan_nvcsws), sum(plan_nivcsws),
               sum(exec_reads), sum(exec_writes),
               sum(exec_user_time), sum(exec_system_time),
               sum(exec_minflts), sum(exec_majflts), sum(exec_nswaps),
               sum(exec_msgsnds), sum(exec_msgrcvs), sum(exec_nsignals),
               sum(exec_nvcsws), sum(exec_nivcsws)
              )::@extschema@.powa_kcache_history_record
            FROM capture
            GROUP BY ts, srvid, top, dbid
    )

    SELECT COUNT(*) into v_rowcount
    FROM capture;

    PERFORM @extschema@.powa_log(format('%s - rowcount: %s',
            v_funcname, v_rowcount));

    IF (_srvid != 0) THEN
        DELETE FROM @extschema@.powa_kcache_src_tmp WHERE srvid = _srvid;
    END IF;

    result := true;
END
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_kcache_snapshot */

/*
 * powa_kcache aggregation
 */
CREATE OR REPLACE FUNCTION @extschema@.powa_kcache_aggregate(_srvid integer)
RETURNS void AS $PROC$
DECLARE
    result     bool;
    v_funcname text := format('@extschema@.%I(%s)',
                              'powa_kcache_aggregate', _srvid);
    v_rowcount bigint;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    -- aggregate metrics table
    INSERT INTO @extschema@.powa_kcache_metrics (coalesce_range, srvid, queryid,
                                            top, dbid, userid, metrics,
                                            mins_in_range, maxs_in_range)
        SELECT tstzrange(min((metrics).ts), max((metrics).ts),'[]'),
        srvid, queryid, top, dbid, userid, array_agg(metrics),
        ROW(min((metrics).ts),
            min((metrics).plan_reads), min((metrics).plan_writes),
            min((metrics).plan_user_time), min((metrics).plan_system_time),
            min((metrics).plan_minflts), min((metrics).plan_majflts),
            min((metrics).plan_nswaps),
            min((metrics).plan_msgsnds), min((metrics).plan_msgrcvs),
            min((metrics).plan_nsignals),
            min((metrics).plan_nvcsws), min((metrics).plan_nivcsws),
            min((metrics).exec_reads), min((metrics).exec_writes),
            min((metrics).exec_user_time), min((metrics).exec_system_time),
            min((metrics).exec_minflts), min((metrics).exec_majflts),
            min((metrics).exec_nswaps),
            min((metrics).exec_msgsnds), min((metrics).exec_msgrcvs),
            min((metrics).exec_nsignals),
            min((metrics).exec_nvcsws), min((metrics).exec_nivcsws)
        )::@extschema@.powa_kcache_history_record,
        ROW(max((metrics).ts),
            max((metrics).plan_reads), max((metrics).plan_writes),
            max((metrics).plan_user_time), max((metrics).plan_system_time),
            max((metrics).plan_minflts), max((metrics).plan_majflts),
            max((metrics).plan_nswaps),
            max((metrics).plan_msgsnds), max((metrics).plan_msgrcvs),
            max((metrics).plan_nsignals),
            max((metrics).plan_nvcsws), max((metrics).plan_nivcsws),
            max((metrics).exec_reads), max((metrics).exec_writes),
            max((metrics).exec_user_time), max((metrics).exec_system_time),
            max((metrics).exec_minflts), max((metrics).exec_majflts),
            max((metrics).exec_nswaps),
            max((metrics).exec_msgsnds), max((metrics).exec_msgrcvs),
            max((metrics).exec_nsignals),
            max((metrics).exec_nvcsws), max((metrics).exec_nivcsws)
        )::@extschema@.powa_kcache_history_record
        FROM @extschema@.powa_kcache_metrics_current
        WHERE srvid = _srvid
        GROUP BY srvid, queryid, top, dbid, userid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s (powa_kcache_metrics) - rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_kcache_metrics_current WHERE srvid = _srvid;

    -- aggregate metrics_db table
    INSERT INTO @extschema@.powa_kcache_metrics_db (srvid, coalesce_range, dbid,
                                               top, metrics,
                                               mins_in_range, maxs_in_range)
        SELECT srvid, tstzrange(min((metrics).ts), max((metrics).ts),'[]'),
        dbid, top, array_agg(metrics),
        ROW(min((metrics).ts),
            min((metrics).plan_reads), min((metrics).plan_writes),
            min((metrics).plan_user_time), min((metrics).plan_system_time),
            min((metrics).plan_minflts), min((metrics).plan_majflts),
            min((metrics).plan_nswaps),
            min((metrics).plan_msgsnds), min((metrics).plan_msgrcvs),
            min((metrics).plan_nsignals),
            min((metrics).plan_nvcsws), min((metrics).plan_nivcsws),
            min((metrics).exec_reads), min((metrics).exec_writes),
            min((metrics).exec_user_time), min((metrics).exec_system_time),
            min((metrics).exec_minflts), min((metrics).exec_majflts),
            min((metrics).exec_nswaps),
            min((metrics).exec_msgsnds), min((metrics).exec_msgrcvs),
            min((metrics).exec_nsignals),
            min((metrics).exec_nvcsws), min((metrics).exec_nivcsws)
        )::@extschema@.powa_kcache_history_record,
        ROW(max((metrics).ts),
            max((metrics).plan_reads), max((metrics).plan_writes),
            max((metrics).plan_user_time), max((metrics).plan_system_time),
            max((metrics).plan_minflts), max((metrics).plan_majflts),
            max((metrics).plan_nswaps),
            max((metrics).plan_msgsnds), max((metrics).plan_msgrcvs),
            max((metrics).plan_nsignals),
            max((metrics).plan_nvcsws), max((metrics).plan_nivcsws),
            max((metrics).exec_reads), max((metrics).exec_writes),
            max((metrics).exec_user_time), max((metrics).exec_system_time),
            max((metrics).exec_minflts), max((metrics).exec_majflts),
            max((metrics).exec_nswaps),
            max((metrics).exec_msgsnds), max((metrics).exec_msgrcvs),
            max((metrics).exec_nsignals),
            max((metrics).exec_nvcsws), max((metrics).exec_nivcsws)
        )::@extschema@.powa_kcache_history_record
        FROM @extschema@.powa_kcache_metrics_current_db
        WHERE srvid = _srvid
        GROUP BY srvid, dbid, top;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s (powa_kcache_metrics_db) - rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_kcache_metrics_current_db WHERE srvid = _srvid;
END
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_kcache_aggregate */

/*
 * powa_kcache purge
 */
CREATE OR REPLACE FUNCTION @extschema@.powa_kcache_purge(_srvid integer)
RETURNS void as $PROC$
DECLARE
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_kcache_purge', _srvid);
    v_rowcount    bigint;
    v_retention   interval;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    SELECT @extschema@.powa_get_server_retention(_srvid) INTO v_retention;

    -- Delete obsolete datas. We only bother with already coalesced data
    DELETE FROM @extschema@.powa_kcache_metrics
    WHERE upper(coalesce_range) < (now() - v_retention)
    AND srvid = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s (powa_kcache_metrics) - rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_kcache_metrics_db
    WHERE upper(coalesce_range) < (now() - v_retention)
    AND srvid = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s (powa_kcache_metrics_db) - rowcount: %s',
            v_funcname, v_rowcount));
END;
$PROC$ language plpgsql; /* end of powa_kcache_purge */

/*
 * powa_kcache reset
 */
CREATE OR REPLACE FUNCTION @extschema@.powa_kcache_reset(_srvid integer)
RETURNS void as $PROC$
DECLARE
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_kcache_reset', _srvid);
    v_rowcount    bigint;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_log('resetting @extschema@.powa_kcache_metrics(' || _srvid || ')');
    DELETE FROM @extschema@.powa_kcache_metrics WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('resetting @extschema@.powa_kcache_metrics_db(' || _srvid || ')');
    DELETE FROM @extschema@.powa_kcache_metrics_db WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('resetting @extschema@.powa_kcache_metrics_current(' || _srvid || ')');
    DELETE FROM @extschema@.powa_kcache_metrics_current WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('resetting @extschema@.powa_kcache_metrics_current_db(' || _srvid || ')');
    DELETE FROM @extschema@.powa_kcache_metrics_current_db WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('resetting @extschema@.powa_kcache_src_tmp(' || _srvid || ')');
    DELETE FROM @extschema@.powa_kcache_src_tmp WHERE srvid = _srvid;
END;
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_kcache_reset */

/* end of pg_stat_kcache integration - part 2 */

/* pg_qualstats integration - part 2 */

/*
 * powa_qualstats utility SRF for aggregating constvalues
 */
CREATE OR REPLACE FUNCTION @extschema@.powa_qualstats_aggregate_constvalues_current(
    IN _srvid integer,
    IN _ts_from timestamptz DEFAULT '-infinity'::timestamptz,
    IN _ts_to timestamptz DEFAULT 'infinity'::timestamptz,
    OUT srvid integer,
    OUT qualid bigint,
    OUT queryid bigint,
    OUT dbid oid,
    OUT userid oid,
    OUT tstzrange tstzrange,
    OUT mu @extschema@.qual_values[],
    OUT mf @extschema@.qual_values[],
    OUT lf @extschema@.qual_values[],
    OUT me @extschema@.qual_values[],
    OUT mer @extschema@.qual_values[],
    OUT men @extschema@.qual_values[])
RETURNS SETOF record STABLE AS $_$
SELECT
    -- Ordered aggregate of top 20 metrics for each kind of stats (most executed, most filetered, least filtered...)
    srvid, qualid, queryid, dbid, userid,
    tstzrange(min(min_constvalues_ts) , max(max_constvalues_ts) ,'[]') ,
    array_agg((constvalues, sum_occurences, sum_execution_count, sum_nbfiltered, avg_mean_err_estimate_ratio, avg_mean_err_estimate_num)::@extschema@.qual_values ORDER BY occurences_rank ASC) FILTER (WHERE occurences_rank <=20)  mu,
    array_agg((constvalues, sum_occurences, sum_execution_count, sum_nbfiltered, avg_mean_err_estimate_ratio, avg_mean_err_estimate_num)::@extschema@.qual_values ORDER BY filtered_rank ASC) FILTER (WHERE filtered_rank <=20)  mf,
    array_agg((constvalues, sum_occurences, sum_execution_count, sum_nbfiltered, avg_mean_err_estimate_ratio, avg_mean_err_estimate_num)::@extschema@.qual_values ORDER BY filtered_rank DESC) FILTER (WHERE filtered_rank >= nb_lines - 20)  lf, -- Keep last 20 lines from the same window function
    array_agg((constvalues, sum_occurences, sum_execution_count, sum_nbfiltered, avg_mean_err_estimate_ratio, avg_mean_err_estimate_num)::@extschema@.qual_values ORDER BY execution_rank ASC) FILTER (WHERE execution_rank <=20)  me,
    array_agg((constvalues, sum_occurences, sum_execution_count, sum_nbfiltered, avg_mean_err_estimate_ratio, avg_mean_err_estimate_num)::@extschema@.qual_values ORDER BY err_estimate_ratio_rank ASC) FILTER (WHERE err_estimate_ratio_rank <=20)  mer,
    array_agg((constvalues, sum_occurences, sum_execution_count, sum_nbfiltered, avg_mean_err_estimate_ratio, avg_mean_err_estimate_num)::@extschema@.qual_values ORDER BY err_estimate_num_rank ASC) FILTER (WHERE err_estimate_num_rank <=20)  men
FROM (
    -- Establish rank for different stats (occurences, execution...) of each constvalues
    SELECT srvid, qualid, queryid, dbid, userid,
        min(mints) OVER (W) min_constvalues_ts, max(maxts) OVER (W) max_constvalues_ts,
        constvalues, sum_occurences, sum_execution_count, sum_nbfiltered, avg_mean_err_estimate_ratio, avg_mean_err_estimate_num,
        row_number() OVER (W ORDER BY sum_occurences DESC) occurences_rank,
        row_number() OVER (W ORDER BY CASE WHEN sum_execution_count = 0 THEN 0 ELSE sum_nbfiltered / sum_execution_count::numeric END DESC) filtered_rank,
        row_number() OVER (W ORDER BY sum_execution_count DESC) execution_rank,
        row_number() OVER (W ORDER BY avg_mean_err_estimate_ratio DESC) err_estimate_ratio_rank,
        row_number() OVER (W ORDER BY avg_mean_err_estimate_num DESC) err_estimate_num_rank,
        sum(1) OVER (W) nb_lines

    FROM (
        -- We group by constvalues and perform some aggregate to have stats on distinct constvalues
        SELECT srvid, qualid, queryid, dbid, userid,constvalues,
            min(ts) mints, max(ts) maxts ,
            sum(occurences) as sum_occurences,
            sum(nbfiltered) as sum_nbfiltered,
            sum(execution_count) as sum_execution_count,
            avg(mean_err_estimate_ratio) as avg_mean_err_estimate_ratio,
            avg(mean_err_estimate_num) as avg_mean_err_estimate_num
        FROM @extschema@.powa_qualstats_constvalues_history_current
        WHERE srvid = _srvid
          AND ts >= _ts_from AND ts <= _ts_to
        GROUP BY srvid, qualid, queryid, dbid, userid,constvalues
        ) distinct_constvalues
    WINDOW W AS (PARTITION BY srvid, qualid, queryid, dbid, userid)
    ) ranked_constvalues
GROUP BY srvid, qualid, queryid, dbid, userid
;
$_$ LANGUAGE sql
SET search_path = pg_catalog; /* end of powa_qualstats_aggregate_constvalues_current */

CREATE OR REPLACE FUNCTION @extschema@.powa_qualstats_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT uniquequalnodeid bigint,
    OUT dbid oid,
    OUT userid oid,
    OUT qualnodeid bigint,
    OUT occurences bigint,
    OUT execution_count bigint,
    OUT nbfiltered bigint,
    OUT mean_err_estimate_ratio double precision,
    OUT mean_err_estimate_num double precision,
    OUT queryid bigint,
    OUT constvalues varchar[],
    OUT quals @extschema@.qual_type[]
) RETURNS SETOF record STABLE AS $PROC$
DECLARE
  is_v2 bool;
  v_pgqs text;
  v_pgss text;
  ratio_col text := 'qs.mean_err_estimate_ratio';
  num_col text := 'qs.mean_err_estimate_num';
  sql text;
BEGIN
    IF (_srvid = 0) THEN
        SELECT substr(extversion, 1, 1)::int >= 2, nspname INTO is_v2, v_pgqs
          FROM pg_catalog.pg_extension e
          JOIN pg_namespace n ON n.oid = e.extnamespace
          WHERE extname = 'pg_qualstats';

        SELECT nspname INTO v_pgss
          FROM pg_catalog.pg_extension e
          JOIN pg_namespace n ON n.oid = e.extnamespace
          WHERE extname = 'pg_stat_statements';

        IF is_v2 IS DISTINCT FROM 'true'::bool THEN
            ratio_col := 'NULL::double precision';
            num_col := 'NULL::double precision';
        END IF;

        sql := format($sql$
            SELECT now(), pgqs.uniquequalnodeid, pgqs.dbid, pgqs.userid,
                pgqs.qualnodeid, pgqs.occurences, pgqs.execution_count,
                pgqs.nbfiltered, pgqs.mean_err_estimate_ratio,
                pgqs.mean_err_estimate_num, pgqs.queryid, pgqs.constvalues,
                pgqs.quals
            FROM (
                SELECT coalesce(i.uniquequalid, i.uniquequalnodeid) AS uniquequalnodeid,
                    i.dbid, i.userid,  coalesce(i.qualid, i.qualnodeid) AS qualnodeid,
                    i.occurences, i.execution_count, i.nbfiltered,
                    i.mean_err_estimate_ratio, i.mean_err_estimate_num,
                    i.queryid,
                    array_agg(i.constvalue order by i.constant_position) AS constvalues,
                    array_agg(ROW(i.relid, i.attnum, i.opno, i.eval_type)::@extschema@.qual_type) AS quals
                FROM
                (
                    SELECT qs.dbid,
                    CASE WHEN lrelid IS NOT NULL THEN lrelid
                        WHEN rrelid IS NOT NULL THEN rrelid
                    END as relid,
                    qs.userid as userid,
                    CASE WHEN lrelid IS NOT NULL THEN lattnum
                        WHEN rrelid IS NOT NULL THEN rattnum
                    END as attnum,
                    qs.opno as opno,
                    qs.qualid as qualid,
                    qs.uniquequalid as uniquequalid,
                    qs.qualnodeid as qualnodeid,
                    qs.uniquequalnodeid as uniquequalnodeid,
                    qs.occurences as occurences,
                    qs.execution_count as execution_count,
                    qs.queryid as queryid,
                    qs.constvalue as constvalue,
                    qs.nbfiltered as nbfiltered,
                    %s AS mean_err_estimate_ratio,
                    %s AS mean_err_estimate_num,
                    qs.eval_type,
                    qs.constant_position
                    FROM %I.pg_qualstats() qs
                    WHERE (qs.lrelid IS NULL) != (qs.rrelid IS NULL)
                ) i
                GROUP BY coalesce(i.uniquequalid, i.uniquequalnodeid),
                    coalesce(i.qualid, i.qualnodeid), i.dbid, i.userid,
                    i.occurences, i.execution_count, i.nbfiltered,
                    i.mean_err_estimate_ratio, i.mean_err_estimate_num,
                    i.queryid
            ) pgqs
            JOIN (
                -- if we use remote capture, powa_statements won't be
                -- populated, so we have to to retrieve the content of both
                -- statements sources.  Since there can (and probably) be
                -- duplicates, we use a UNION on purpose
                SELECT s1.queryid, s1.dbid, s1.userid
                    FROM %I.pg_stat_statements s1
                UNION
                SELECT s2.queryid, s2.dbid, s2.userid
                    FROM @extschema@.powa_statements s2 WHERE s2.srvid = 0
            ) s USING(queryid, dbid, userid)
        -- we don't gather quals for databases that have been dropped
        JOIN pg_catalog.pg_database d ON d.oid = s.dbid
        JOIN pg_catalog.pg_roles r ON s.userid = r.oid
          AND NOT (r.rolname = ANY (string_to_array(
                    @extschema@.powa_get_guc('powa.ignored_users', ''),
                    ',')))
        WHERE pgqs.dbid NOT IN (SELECT oid FROM @extschema@.powa_databases WHERE dropped IS NOT NULL)
        $sql$, ratio_col, num_col, v_pgqs, v_pgss);
        RETURN QUERY EXECUTE sql;
    ELSE
        RETURN QUERY
            SELECT pgqs.ts, pgqs.uniquequalnodeid, pgqs.dbid, pgqs.userid,
                pgqs.qualnodeid, pgqs.occurences, pgqs.execution_count,
                pgqs.nbfiltered, pgqs.mean_err_estimate_ratio,
                pgqs.mean_err_estimate_num, pgqs.queryid, pgqs.constvalues,
                pgqs.quals
            FROM @extschema@.powa_qualstats_src_tmp pgqs
        WHERE pgqs.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_qualstats_src */

CREATE OR REPLACE FUNCTION @extschema@.powa_qualstats_snapshot(_srvid integer) RETURNS void as $PROC$
DECLARE
    result     bool;
    v_schema   text;
    v_funcname text := format('@extschema.%I(%s)',
                              'powa_qualstats_snapshot', _srvid);
    v_rowcount bigint;
BEGIN
  PERFORM @extschema@.powa_log(format('running %s', v_funcname));

  PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

  WITH capture AS (
    SELECT *
    FROM @extschema@.powa_qualstats_src(_srvid) q
    WHERE EXISTS (SELECT 1
      FROM @extschema@.powa_statements s
      WHERE s.srvid = _srvid
      AND q.queryid = s.queryid
      AND q.dbid = s.dbid
      AND q.userid = s.userid)
  ),
  missing_quals AS (
      INSERT INTO @extschema@.powa_qualstats_quals (srvid, qualid, queryid, dbid, userid, quals)
        SELECT DISTINCT _srvid AS srvid, qs.qualnodeid, qs.queryid, qs.dbid, qs.userid,
          array_agg(DISTINCT q::@extschema@.qual_type)
        FROM capture qs,
        LATERAL (SELECT (unnest(quals)).*) as q
        WHERE NOT EXISTS (
          SELECT 1
          FROM @extschema@.powa_qualstats_quals nh
          WHERE nh.srvid = _srvid
            AND nh.qualid = qs.qualnodeid
            AND nh.queryid = qs.queryid
            AND nh.dbid = qs.dbid
            AND nh.userid = qs.userid
        )
        GROUP BY srvid, qualnodeid, qs.queryid, qs.dbid, qs.userid
      RETURNING *
  ),
  by_qual AS (
      INSERT INTO @extschema@.powa_qualstats_quals_history_current (srvid, qualid, queryid,
        dbid, userid, ts, occurences, execution_count, nbfiltered,
        mean_err_estimate_ratio, mean_err_estimate_num)
      SELECT _srvid AS srvid, qs.qualnodeid, qs.queryid, qs.dbid, qs.userid,
          ts, sum(occurences), sum(execution_count), sum(nbfiltered),
          avg(mean_err_estimate_ratio), avg(mean_err_estimate_num)
        FROM capture as qs
        GROUP BY srvid, ts, qualnodeid, qs.queryid, qs.dbid, qs.userid
      RETURNING *
  ),
  by_qual_with_const AS (
      INSERT INTO @extschema@.powa_qualstats_constvalues_history_current(srvid, qualid,
        queryid, dbid, userid, ts, occurences, execution_count, nbfiltered,
        mean_err_estimate_ratio, mean_err_estimate_num, constvalues)
      SELECT _srvid, qualnodeid, qs.queryid, qs.dbid, qs.userid, ts,
        occurences, execution_count, nbfiltered, mean_err_estimate_ratio,
        mean_err_estimate_num, constvalues
      FROM capture as qs
  )
  SELECT COUNT(*) into v_rowcount
  FROM capture;

  PERFORM @extschema@.powa_log(format('%s - rowcount: %s',
        v_funcname, v_rowcount));

    IF (_srvid != 0) THEN
        DELETE FROM @extschema@.powa_qualstats_src_tmp WHERE srvid = _srvid;
    END IF;

  result := true;

  -- pg_qualstats metrics are not accumulated, so we force a reset after every
  -- snapshot.  For local snapshot this is done here, remote snapshots will
  -- rely on the collector doing it through query_cleanup.
  IF (_srvid = 0) THEN
    SELECT n.nspname INTO STRICT v_schema
        FROM pg_catalog.pg_extension e
        JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace
        AND e.extname = 'pg_qualstats';
    PERFORM format('%I.pg_qualstats_reset()', v_schema);
  END IF;
END
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_qualstats_snapshot */

/*
 * powa_qualstats aggregate
 */
CREATE OR REPLACE FUNCTION @extschema@.powa_qualstats_aggregate(_srvid integer)
RETURNS void AS $PROC$
DECLARE
  result bool;
BEGIN
  PERFORM @extschema@.powa_log('running powa_qualstats_aggregate(' || _srvid || ')');

  PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

  INSERT INTO @extschema@.powa_qualstats_constvalues_history (
      srvid, qualid, queryid, dbid, userid, coalesce_range, most_used,
      most_filtering, least_filtering, most_executed, most_errestim_ratio,
    most_errestim_num)
    SELECT * FROM @extschema@.powa_qualstats_aggregate_constvalues_current(_srvid)
    WHERE srvid = _srvid;

  INSERT INTO @extschema@.powa_qualstats_quals_history (srvid, qualid, queryid, dbid,
      userid, coalesce_range, records, mins_in_range, maxs_in_range)
    SELECT srvid, qualid, queryid, dbid, userid, tstzrange(min(ts),
      max(ts),'[]'),
      array_agg((ts, occurences, execution_count, nbfiltered,
            mean_err_estimate_ratio,
            mean_err_estimate_num)::@extschema@.powa_qualstats_history_record),
    ROW(min(ts), min(occurences), min(execution_count), min(nbfiltered),
        min(mean_err_estimate_ratio), min(mean_err_estimate_num)
    )::@extschema@.powa_qualstats_history_record,
    ROW(max(ts), max(occurences), max(execution_count), max(nbfiltered),
        max(mean_err_estimate_ratio), max(mean_err_estimate_num)
    )::@extschema@.powa_qualstats_history_record
    FROM @extschema@.powa_qualstats_quals_history_current
    WHERE srvid = _srvid
    GROUP BY srvid, qualid, queryid, dbid, userid;

  DELETE FROM @extschema@.powa_qualstats_constvalues_history_current WHERE srvid = _srvid;
  DELETE FROM @extschema@.powa_qualstats_quals_history_current WHERE srvid = _srvid;
END
$PROC$ language plpgsql
SET search_path = pg_catalog
SET search_path = pg_catalog; /* end of powa_qualstats_aggregate */

/*
 * powa_qualstats_purge
 */
CREATE OR REPLACE FUNCTION @extschema@.powa_qualstats_purge(_srvid integer)
RETURNS void as $PROC$
DECLARE
    v_retention   interval;
BEGIN
    PERFORM @extschema@.powa_log('running powa_qualstats_purge(' || _srvid || ')');

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    SELECT @extschema@.powa_get_server_retention(_srvid) INTO v_retention;

    -- Delete obsolete datas. We only bother with already coalesced data
    DELETE FROM @extschema@.powa_qualstats_constvalues_history
    WHERE upper(coalesce_range) < (now() - v_retention)
    AND srvid = _srvid;

    DELETE FROM @extschema@.powa_qualstats_quals_history
    WHERE upper(coalesce_range) < (now() - v_retention)
    AND srvid = _srvid;
END;
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_qualstats_purge */

/*
 * powa_qualstats_reset
 */
CREATE OR REPLACE FUNCTION @extschema@.powa_qualstats_reset(_srvid integer)
RETURNS void as $PROC$
BEGIN
  PERFORM @extschema@.powa_log('running powa_qualstats_reset(' || _srvid || ')');

  PERFORM @extschema@.powa_log('resetting powa_qualstats_quals(' || _srvid || ')');
  DELETE FROM @extschema@.powa_qualstats_quals WHERE srvid = _srvid;
  -- cascaded :
  -- powa_qualstats_quals_history
  -- powa_qualstats_quals_history_current
  -- powa_qualstats_constvalues_history
  -- powa_qualstats_constvalues_history_current

  PERFORM @extschema@.powa_log('resetting powa_qualstats_src_tmp(' || _srvid || ')');
  DELETE FROM @extschema@.powa_qualstats_src_tmp WHERE srvid = _srvid;
END;
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_qualstats_reset */

/* end of pg_qualstats integration - part 2 */

-- nothing to do

/* end pg_track_settings integration */

/* pg_wait_sampling integration - part 2 */

CREATE OR REPLACE FUNCTION @extschema@.powa_wait_sampling_src(IN _srvid integer,
    OUT ts timestamp with time zone,
    OUT dbid oid,
    OUT event_type text,
    OUT event text,
    OUT queryid bigint,
    OUT count numeric
) RETURNS SETOF RECORD STABLE AS $PROC$
DECLARE
  v_pgws text;
  v_pgss text;
BEGIN
    IF (_srvid = 0) THEN
        SELECT nspname INTO STRICT v_pgws
        FROM pg_catalog.pg_extension e
        JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace
        WHERE e.extname = 'pg_wait_sampling';

        SELECT nspname INTO STRICT v_pgss
        FROM pg_catalog.pg_extension e
        JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace
        WHERE e.extname = 'pg_stat_statements';

        RETURN QUERY EXECUTE format($$
            -- the various background processes report wait events but don't have
            -- associated queryid.  Gather them all under a fake 0 dbid
            SELECT now(), COALESCE(pgss.dbid, 0) AS dbid, s.event_type,
                s.event, s.queryid, sum(s.count) as count
            FROM %I.pg_wait_sampling_profile s
            -- pg_wait_sampling doesn't offer a per (userid, dbid, queryid) view,
            -- only per pid, but pid can be reused for different databases or users
            -- so we cannot deduce db or user from it.  However, queryid should be
            -- unique across differet databases, so we retrieve the dbid this way.
            -- Note that the same queryid can exists for multiple entries if
            -- multiple users execute the query, so it's critical to retrieve a
            -- single row from pg_stat_statements per (dbid, queryid)
            LEFT JOIN (SELECT DISTINCT s2.dbid, s2.queryid
                FROM %I.pg_stat_statements(false) s2
            ) pgss ON pgss.queryid = s.queryid
            WHERE s.event_type IS NOT NULL AND s.event IS NOT NULL
            AND COALESCE(pgss.dbid, 0) NOT IN (
                SELECT oid FROM @extschema@.powa_databases
                WHERE dropped IS NOT NULL
            )
            GROUP BY pgss.dbid, s.event_type, s.event, s.queryid
          $$, v_pgws, v_pgss);
    ELSE
        RETURN QUERY
        SELECT s.ts, s.dbid, s.event_type, s.event, s.queryid, s.count
        FROM @extschema@.powa_wait_sampling_src_tmp s
        WHERE s.srvid = _srvid;
    END IF;
END;
$PROC$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_wait_sampling_src */

/*
 * powa_wait_sampling snapshot collection.
 */
CREATE OR REPLACE FUNCTION @extschema@.powa_wait_sampling_snapshot(_srvid integer) RETURNS void as $PROC$
DECLARE
  result bool;
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_wait_sampling_snapshot', _srvid);
    v_rowcount    bigint;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    WITH capture AS (
        SELECT *
        FROM @extschema@.powa_wait_sampling_src(_srvid)
    ),

    by_query AS (
        INSERT INTO @extschema@.powa_wait_sampling_history_current (srvid, queryid, dbid,
                event_type, event, record)
            SELECT _srvid, queryid, dbid, event_type, event,
                (ts, count)::@extschema@.powa_wait_sampling_history_record
            FROM capture
    ),

    by_database AS (
        INSERT INTO @extschema@.powa_wait_sampling_history_current_db (srvid, dbid,
                event_type, event, record)
            SELECT _srvid AS srvid, dbid, event_type, event,
                (ts, sum(count))::@extschema@.powa_wait_sampling_history_record
            FROM capture
            GROUP BY srvid, ts, dbid, event_type, event
    )

    SELECT COUNT(*) into v_rowcount
    FROM capture;

    PERFORM @extschema@.powa_log(format('%s - rowcount: %s',
            v_funcname, v_rowcount));

    IF (_srvid != 0) THEN
        DELETE FROM @extschema@.powa_wait_sampling_src_tmp WHERE srvid = _srvid;
    END IF;

    result := true;
END
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_wait_sampling_snapshot */

/*
 * powa_wait_sampling aggregation
 */
CREATE OR REPLACE FUNCTION @extschema@.powa_wait_sampling_aggregate(_srvid integer)
RETURNS void AS $PROC$
DECLARE
    result     bool;
    v_funcname text := format('@extschema@.%I(%s)',
                              'powa_wait_sampling_aggregate', _srvid);
    v_rowcount bigint;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    -- aggregate history table
    INSERT INTO @extschema@.powa_wait_sampling_history (coalesce_range, srvid, queryid,
            dbid, event_type, event, records, mins_in_range, maxs_in_range)
        SELECT tstzrange(min((record).ts), max((record).ts),'[]'),
            srvid, queryid, dbid, event_type, event, array_agg(record),
        ROW(min((record).ts),
            min((record).count))::@extschema@.powa_wait_sampling_history_record,
        ROW(max((record).ts),
            max((record).count))::@extschema@.powa_wait_sampling_history_record
        FROM @extschema@.powa_wait_sampling_history_current
        WHERE srvid = _srvid
        GROUP BY srvid, queryid, dbid, event_type, event;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s (powa_wait_sampling_history) - rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_wait_sampling_history_current WHERE srvid = _srvid;

    -- aggregate history_db table
    INSERT INTO @extschema@.powa_wait_sampling_history_db (coalesce_range, srvid, dbid,
            event_type, event, records, mins_in_range, maxs_in_range)
        SELECT tstzrange(min((record).ts), max((record).ts),'[]'), srvid, dbid,
            event_type, event, array_agg(record),
        ROW(min((record).ts),
            min((record).count))::@extschema@.powa_wait_sampling_history_record,
        ROW(max((record).ts),
            max((record).count))::@extschema@.powa_wait_sampling_history_record
        FROM @extschema@.powa_wait_sampling_history_current_db
        WHERE srvid = _srvid
        GROUP BY srvid, dbid, event_type, event;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s (powa_wait_sampling_history_db) - rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_wait_sampling_history_current_db WHERE srvid = _srvid;
END
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_wait_sampling_aggregate */

/*
 * powa_wait_sampling purge
 */
CREATE OR REPLACE FUNCTION @extschema@.powa_wait_sampling_purge(_srvid integer)
RETURNS void as $PROC$
DECLARE
    v_funcname    text := format('@extschema.%I(%s)',
                                 'powa_wait_sampling_purge', _srvid);
    v_rowcount    bigint;
    v_retention   interval;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid);

    SELECT @extschema@.powa_get_server_retention(_srvid) INTO v_retention;

    -- Delete obsolete datas. We only bother with already coalesced data
    DELETE FROM @extschema@.powa_wait_sampling_history
    WHERE upper(coalesce_range) < (now() - v_retention)
    AND srvid = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s (powa_wait_sampling_history) - rowcount: %s',
            v_funcname, v_rowcount));

    DELETE FROM @extschema@.powa_wait_sampling_history_db
    WHERE upper(coalesce_range) < (now() - v_retention)
    AND srvid = _srvid;

    GET DIAGNOSTICS v_rowcount = ROW_COUNT;
    PERFORM @extschema@.powa_log(format('%s (powa_wait_sampling_history_db) - rowcount: %s',
            v_funcname, v_rowcount));
END;
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_wait_sampling_purge */

/*
 * powa_wait_sampling reset
 */
CREATE OR REPLACE FUNCTION @extschema@.powa_wait_sampling_reset(_srvid integer)
RETURNS void as $PROC$
DECLARE
    v_funcname    text := format('@extschema@.%I(%s)',
                                 'powa_wait_sampling_reset', _srvid);
    v_rowcount    bigint;
BEGIN
    PERFORM @extschema@.powa_log(format('running %s', v_funcname));

    PERFORM @extschema@.powa_log('resetting powa_wait_sampling_history(' || _srvid || ')');
    DELETE FROM @extschema@.powa_wait_sampling_history WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('resetting powa_wait_sampling_history_db(' || _srvid || ')');
    DELETE FROM @extschema@.powa_wait_sampling_history_db WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('resetting powa_wait_sampling_history_current(' || _srvid || ')');
    DELETE FROM @extschema@.powa_wait_sampling_history_current WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('resetting powa_wait_sampling_history_current_db(' || _srvid || ')');
    DELETE FROM @extschema@.powa_wait_sampling_history_current_db WHERE srvid = _srvid;

    PERFORM @extschema@.powa_log('resetting powa_wait_sampling_src_tmp(' || _srvid || ')');
    DELETE FROM @extschema@.powa_wait_sampling_src_tmp WHERE srvid = _srvid;
END;
$PROC$ language plpgsql
SET search_path = pg_catalog; /* end of powa_wait_sampling_reset */

/* end of pg_wait_sampling integration - part 2 */

---------------
-- ACL handling
---------------

/*
 * powa_grant() will grant the appropriate ACL to the various powa_* pseudo
 * predefined roles.
 *
 * We try to avoid relying on external extensions like hstore to not have to
 * handle the possibility of it installed in some custom and not visible
 * schema, so the code is a bit more verbose than needed.
 */
CREATE FUNCTION @extschema@.powa_grant() RETURNS void
AS $$
DECLARE
    relname name;
    relkind char;
    powa_role name;
    rolname name;
    admin_role name;
    read_all_data_role name;
    read_all_metrics_role name;
    write_all_data_role name;
    snapshot_role name;
    signal_backend_role name;
    v_nb integer;
BEGIN
    FOR powa_role, rolname IN SELECT pr.powa_role, pr.rolname
                              FROM @extschema@.powa_roles pr
    LOOP
        IF rolname IS NULL THEN
            RAISE EXCEPTION 'powa_role % is NULL', powa_role;
        END IF;

        IF powa_role = 'powa_admin' THEN
            admin_role = rolname;
        ELSIF powa_role = 'powa_read_all_data' THEN
            read_all_data_role = rolname;
        ELSIF powa_role = 'powa_read_all_metrics' THEN
            read_all_metrics_role = rolname;
        ELSIF powa_role = 'powa_write_all_data' THEN
            write_all_data_role = rolname;
        ELSIF powa_role = 'powa_snapshot' THEN
            snapshot_role = rolname;
        ELSIF powa_role = 'powa_signal_backend' THEN
            signal_backend_role = rolname;
        ELSE
            RAISE EXCEPTION 'Unexpected powa_role %', powa_role;
        END IF;
    END LOOP;

    FOR relname, relkind IN
        SELECT c.relname, c.relkind
        FROM pg_depend d
        JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass
            AND e.oid = d.refobjid
            AND e.extname = 'powa'
        JOIN pg_class c ON d.classid = 'pg_class'::regclass
            AND c.oid = d.objid
    LOOP
        EXECUTE format('GRANT ALL ON @extschema@.%I TO %I',
                       relname, admin_role);

        IF relkind = 'S' THEN
            EXECUTE format('GRANT USAGE, SELECT, UPDATE ON @extschema@.%I TO %I',
                           relname, write_all_data_role);
        ELSE
            EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE '
                           'ON @extschema@.%I TO %I',
                           relname, write_all_data_role);
            EXECUTE format('REVOKE REFERENCES, TRIGGER ON @extschema@.%I FROM %I',
                           relname, write_all_data_role);
            EXECUTE format('REVOKE REFERENCES, TRIGGER ON @extschema@.%I FROM %I',
                           relname, snapshot_role);
            -- powa_snapshot can only write to snapshot-related data
            IF relname IN ('powa_roles', 'powa_servers', 'powa_extensions',
                           'powa_extension_functions', 'powa_extension_config',
                            'powa_modules', 'powa_module_config',
                            'powa_module_functions', 'powa_db_modules',
                            'powa_db_module_config',
                            'powa_db_module_functions',
                            'powa_db_module_src_queries', 'powa_catalogs',
                            'powa_catalog_src_queries')
                OR relkind = 'v'
            THEN
                EXECUTE format('GRANT SELECT '
                               'ON @extschema@.%I TO %I',
                               relname, snapshot_role);
            ELSE
                EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE '
                               'ON @extschema@.%I TO %I',
                               relname, snapshot_role);
            END IF;
        END IF;

        EXECUTE format('REVOKE ALL ON @extschema@.%I FROM %I',
                       relname, signal_backend_role);

        -- powa_read_all_data only has SELECT privilege on non *_src_tmp tables
        --
        -- powa_read_all_metrics on has SELECT privileges on non *_src_tmp
        -- tables and non pg_qualstats constvalues related tables
        IF relname LIKE '%\_src\_tmp' THEN
            EXECUTE format('REVOKE ALL ON @extschema@.%I FROM %I',
                           relname, read_all_data_role);
            EXECUTE format('REVOKE ALL ON @extschema@.%I FROM %I',
                           relname, read_all_metrics_role);
        ELSIF relname LIKE '%qualstats\_constvalues%' THEN
            EXECUTE format('REVOKE ALL ON @extschema@.%I FROM %I',
                           relname, read_all_metrics_role);
            EXECUTE format('GRANT SELECT ON @extschema@.%I TO %I',
                           relname, read_all_data_role);
        ELSE
            IF relkind = 'S' THEN
                EXECUTE format('REVOKE ALL ON @extschema@.%I FROM %I',
                               relname, read_all_data_role);
                EXECUTE format('REVOKE ALL ON @extschema@.%I FROM %I',
                               relname, read_all_metrics_role);
            ELSE
                EXECUTE format('GRANT SELECT ON @extschema@.%I TO %I',
                               relname, read_all_data_role);
                EXECUTE format('GRANT SELECT ON @extschema@.%I TO %I',
                               relname, read_all_metrics_role);
                EXECUTE format('REVOKE INSERT, UPDATE, DELETE, TRUNCATE, '
                               'REFERENCES, TRIGGER ON @extschema@.%I FROM %I',
                               relname, read_all_data_role);
                EXECUTE format('REVOKE INSERT, UPDATE, DELETE, TRUNCATE, '
                               'REFERENCES, TRIGGER ON @extschema@.%I FROM %I',
                               relname, read_all_metrics_role);
            END IF;
        END IF;
    END LOOP;
END;
$$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_grant() */

/*
 * powa_revoke() will revoke any ACL from the various powa_* pseudo
 * predefined roles.
 *
 * This is mostly intended to help dropping the powa_* pseudo predefine roles.
 *
 * We don't try to revoke any ACL on non-powa relations, as powa_grant() won't
 * try to do that.  If users added some extra ACL they will have to take care
 * of it themselves.
 */
CREATE FUNCTION @extschema@.powa_revoke() RETURNS void
AS $$
DECLARE
    relname name;
    powa_role name;
    rolname name;
    admin_role name;
    read_all_data_role name;
    read_all_metrics_role name;
    write_all_data_role name;
    snapshot_role name;
    signal_backend_role name;
    v_nb integer;
BEGIN
    FOR powa_role, rolname IN SELECT pr.powa_role, pr.rolname
                              FROM @extschema@.powa_roles pr
    LOOP
        IF rolname IS NULL THEN
            RAISE EXCEPTION 'powa_role % is NULL', powa_role;
        END IF;

        IF powa_role = 'powa_admin' THEN
            admin_role = rolname;
        ELSIF powa_role = 'powa_read_all_data' THEN
            read_all_data_role = rolname;
        ELSIF powa_role = 'powa_read_all_metrics' THEN
            read_all_metrics_role = rolname;
        ELSIF powa_role = 'powa_write_all_data' THEN
            write_all_data_role = rolname;
        ELSIF powa_role = 'powa_snapshot' THEN
            snapshot_role = rolname;
        ELSIF powa_role = 'powa_signal_backend' THEN
            signal_backend_role = rolname;
        ELSE
            RAISE EXCEPTION 'Unexpected powa_role %', powa_role;
        END IF;
    END LOOP;

    FOR relname IN
        SELECT c.relname
        FROM pg_depend d
        JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass
            AND e.oid = d.refobjid
            AND e.extname = 'powa'
        JOIN pg_class c ON d.classid = 'pg_class'::regclass
            AND c.oid = d.objid
    LOOP
        EXECUTE format('REVOKE ALL ON @extschema@.%I FROM %I',
                       relname, admin_role);
        EXECUTE format('REVOKE ALL ON @extschema@.%I FROM %I',
                       relname, read_all_data_role);
        EXECUTE format('REVOKE ALL ON @extschema@.%I FROM %I',
                       relname, read_all_metrics_role);
        EXECUTE format('REVOKE ALL ON @extschema@.%I FROM %I',
                       relname, write_all_data_role);
        EXECUTE format('REVOKE ALL ON @extschema@.%I FROM %I',
                       relname, snapshot_role);
        EXECUTE format('REVOKE ALL ON @extschema@.%I FROM %I',
                       relname, signal_backend_role);
    END LOOP;
END;
$$ LANGUAGE plpgsql
SET search_path = pg_catalog; /* end of powa_revoke() */

-- mass set proper ACL IIF none of the default pseudo predefined roles exist
DO
$$
DECLARE
    v_nb int;
BEGIN
    SELECT count(*) INTO v_nb
    FROM @extschema@.powa_roles p
    JOIN pg_catalog.pg_roles c ON c.rolname = p.powa_role;

    IF v_nb = 0 THEN
        RAISE NOTICE 'Creating default powa pseudo predefined roles';
        PERFORM @extschema@.setup_powa_roles();
    ELSE
        RAISE NOTICE 'Skipping default powa pseudo predefined roles';
    END IF;
END;
$$ LANGUAGE plpgsql;

-- Finally, activate any supported extension that's already locally installed.
SELECT @extschema@.powa_activate_extension(0, extname)
FROM @extschema@.powa_extensions p
JOIN pg_catalog.pg_extension e USING (extname);
