----------------------------------------------------------------------
-- File      :   pg-db-{{ database.name }}.sql
-- Desc      :   creation sql script for db {{ database.name }}
-- Time      :   {{ '%Y-%m-%d %H:%M' | strftime }}
-- Host      :   {{ pg_cluster }}-{{ pg_seq }} @ {{ inventory_hostname }}:{{ pg_port|default(5432) }}
-- Path      :   /pg/tmp/pg-db-{{ database.name }}.sql
-- Note      :   ANSIBLE MANAGED, DO NOT CHANGE!
-- License   :   Apache-2.0 @ https://pigsty.io/docs/about/license/
-- Copyright :   2018-2026  Ruohang Feng / Vonng (rh@vonng.com)
----------------------------------------------------------------------


--==================================================================--
--                            EXECUTION                             --
--==================================================================--
-- run as dbsu (postgres by default)
-- createdb -w -p {{ pg_port|default(5432) }} {% if 'owner' in database and database.owner != '' %}-O "{{ database.owner      }}" {% endif %}
{% if 'template'   in database and database.template   is not none and database.template != ''   %}-T '{{ database.template   }}' {% endif %}
{% if 'encoding'   in database and database.encoding   is not none and database.encoding != ''   %}-E '{{ database.encoding   }}' {% endif %}
{% if 'locale'     in database and database.locale     is not none and database.locale != ''     %}-l '{{ database.locale     }}' {% endif %}
{% if 'lc_collate' in database and database.lc_collate is not none and database.lc_collate != '' %}--lc-collate '{{ database.lc_collate }}' {% endif %}
{% if 'lc_ctype'   in database and database.lc_ctype   is not none and database.lc_ctype != ''   %}--lc-ctype '{{ database.lc_ctype   }}' {% endif %}
{% if 'tablespace' in database and database.tablespace is not none and database.tablespace != '' %}-D '{{ database.tablespace }}' {% endif %}
{% if 'strategy'   in database and database.strategy   is not none and database.strategy != ''   %}-S '{{ database.strategy   }}' {% endif %}
'{{ database.name }}';
-- psql {{ database.name }} -p {{ pg_port|default(5432) }} -AXtwqf /pg/tmp/pg-db-{{ database.name }}.sql
{% if 'baseline' in database and database.baseline != '' %}
-- psql {{ database.name }} -p {{ pg_port|default(5432) }} -AXtwqf /pg/tmp/pg-db-{{ database.name }}-baseline.sql
{% endif %}

--==================================================================--
--                         CREATE DATABASE                          --
--==================================================================--
-- create database with following commands
-- CREATE DATABASE "{{ database.name }}" {% if 'owner' in  database and database.owner != '' %}OWNER "{{ database.owner }}" {% endif %}
{% if 'template'        in database and database.template is not none        and database.template != ''    %}TEMPLATE "{{ database.template }}" {% endif %}
{% if 'encoding'        in database and database.encoding is not none        and database.encoding != ''    %}ENCODING '{{ database.encoding }}' {% endif %}
{% if 'locale'          in database and database.locale is not none          and database.locale != ''      %}LOCALE "{{ database.locale }}" {% endif %}
{% if 'lc_collate'      in database and database.lc_collate is not none      and database.lc_collate != ''  %}LC_COLLATE "{{ database.lc_collate }}" {% endif %}
{% if 'lc_ctype'        in database and database.lc_ctype is not none        and database.lc_ctype != ''    %}LC_CTYPE "{{ database.lc_ctype }}" {% endif %}
{% if 'tablespace'      in database and database.tablespace is not none      and database.tablespace != ''  %}TABLESPACE "{{ database.tablespace }}" {% endif %}
{% if 'is_template'     in database and database.is_template is not none     and database.is_template != '' %}IS_TEMPLATE {{ database.is_template|lower }} {% endif %}
{% if 'strategy'        in database and database.strategy is not none        and database.strategy != ''        and pg_version|default(18)|int >= 15 %}STRATEGY {{ database.strategy|upper }} {% endif %}
{% if 'locale_provider' in database and database.locale_provider is not none and database.locale_provider != '' and pg_version|default(18)|int >= 15 %}LOCALE_PROVIDER '{{ database.locale_provider }}' {% endif %}
{% if 'icu_locale'      in database and database.icu_locale is not none      and database.icu_locale != ''      and pg_version|default(18)|int >= 15 %}ICU_LOCALE '{{ database.icu_locale }}' {% endif %}
{% if 'icu_rules'       in database and database.icu_rules is not none       and database.icu_rules != ''       and pg_version|default(18)|int >= 16 %}ICU_RULES '{{ database.icu_rules }}' {% endif %}
{% if 'builtin_locale'  in database and database.builtin_locale is not none  and database.builtin_locale != ''  and pg_version|default(18)|int >= 17 %}BUILTIN_LOCALE '{{ database.builtin_locale }}' {% endif %}
;
-- following commands are executed within database "{{ database.name }}"


--==================================================================--
--                         ALTER DATABASE                           --
--==================================================================--
-- owner
{% if 'owner' in database and database.owner is not none and database.owner != '' %}
ALTER DATABASE "{{ database.name }}" OWNER TO "{{ database.owner }}";
GRANT ALL PRIVILEGES ON DATABASE "{{ database.name }}" TO "{{ database.owner }}";
{% endif %}

-- tablespace
{% if 'tablespace' in database and database.tablespace is not none and database.tablespace != '' %}
ALTER DATABASE "{{ database.name }}" SET TABLESPACE "{{ database.tablespace }}";
{% endif %}

-- allow connection
{% if 'allowconn' in database and database.allowconn is not none %}
ALTER DATABASE "{{ database.name }}" ALLOW_CONNECTIONS {{ 'true' if database.allowconn|bool else 'false' }};
{% endif %}

-- connection limit
{% if 'connlimit' in database and database.connlimit is not none %}
ALTER DATABASE "{{ database.name }}" CONNECTION LIMIT {{ database.connlimit | int }};
{% endif %}

-- is_template
{% if 'is_template' in database and database.is_template is not none %}
ALTER DATABASE "{{ database.name }}" IS_TEMPLATE {{ database.is_template|lower }};
{% endif %}

-- parameters
{% if 'parameters' in database and database.parameters is not none %}
{% set list_params = ['search_path', 'temp_tablespaces', 'local_preload_libraries', 'session_preload_libraries', 'log_destination'] %}
{% for key, value in database.parameters.items() %}
{% if value is not none %}
{% if value | string | upper == 'DEFAULT' %}
ALTER DATABASE "{{ database.name }}" SET "{{ key }}" = DEFAULT;
{% elif key in list_params %}
ALTER DATABASE "{{ database.name }}" SET "{{ key }}" = {{ value }};
{% else %}
ALTER DATABASE "{{ database.name }}" SET "{{ key }}" = '{{ value | replace("'", "''") }}';
{% endif %}
{% endif %}
{% endfor %}{% endif %}

-- comment
{% if 'comment' in database and database.comment is not none %}
COMMENT ON DATABASE "{{ database.name }}" IS '{{ database.comment | replace("'", "''") }}';
{% else %}
COMMENT ON DATABASE "{{ database.name }}" IS 'business database {{ database.name | replace("'", "''") }}';
{% endif %}


--==================================================================--
--                       REVOKE/GRANT CONNECT                       --
--==================================================================--
{% if 'revokeconn' in database and database.revokeconn is not none %}
{% if database.revokeconn|bool %}
-- revoke public connect privilege
REVOKE CONNECT ON DATABASE "{{ database.name }}" FROM PUBLIC;

-- replicator, monitor have connect privilege
GRANT CONNECT ON DATABASE "{{ database.name }}" TO "{{ pg_replication_username|default('replicator') }}";
GRANT CONNECT ON DATABASE "{{ database.name }}" TO "{{ pg_monitor_username|default('dbuser_monitor') }}";

-- admin have connect privilege with grant option
GRANT CONNECT ON DATABASE "{{ database.name }}" TO "{{ pg_admin_username|default('dbuser_dba') }}" WITH GRANT OPTION;

-- owner have connect privilege with grant option if exists
{% if 'owner' in  database and database.owner is not none and database.owner != '' %}
GRANT CONNECT ON DATABASE "{{ database.name }}" TO "{{ database.owner }}" WITH GRANT OPTION;
{% endif %}
{% else %}
-- restore public connect privilege
GRANT CONNECT ON DATABASE "{{ database.name }}" TO PUBLIC;
{% endif %}
{% endif %}

--==================================================================--
--                       REVOKE/GRANT CREATE                        --
--==================================================================--
-- revoke create (schema) privilege from public
REVOKE CREATE ON DATABASE "{{ database.name }}" FROM PUBLIC;

-- only admin role have create privilege
GRANT CREATE ON DATABASE "{{ database.name }}" TO "dbrole_admin";

-- revoke public schema creation
REVOKE CREATE ON SCHEMA public FROM PUBLIC;

-- admin can create objects in public schema
GRANT CREATE ON SCHEMA public TO "dbrole_admin";


--==================================================================--
--                          Default Privileges                      --
--==================================================================--
{% if 'owner' in database and database.owner is not none and database.owner != '' %}
{% if pg_default_privileges is defined and pg_default_privileges is iterable and pg_default_privileges is not string and pg_default_privileges | length > 0 %}
-- setup default privileges for database owner from pg_default_privileges
{% for priv in pg_default_privileges %}
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" {{ priv }};
{% endfor %}
{% else %}
-- setup default privileges for database owner from default settings
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT USAGE      ON SCHEMAS   TO dbrole_readonly;
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT SELECT     ON TABLES    TO dbrole_readonly;
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT SELECT     ON SEQUENCES TO dbrole_readonly;
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT EXECUTE    ON FUNCTIONS TO dbrole_readonly;
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT USAGE      ON SCHEMAS   TO dbrole_offline;
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT SELECT     ON TABLES    TO dbrole_offline;
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT SELECT     ON SEQUENCES TO dbrole_offline;
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT EXECUTE    ON FUNCTIONS TO dbrole_offline;
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT INSERT     ON TABLES    TO dbrole_readwrite;
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT UPDATE     ON TABLES    TO dbrole_readwrite;
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT DELETE     ON TABLES    TO dbrole_readwrite;
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT USAGE      ON SEQUENCES TO dbrole_readwrite;
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT UPDATE     ON SEQUENCES TO dbrole_readwrite;
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT TRUNCATE   ON TABLES    TO dbrole_admin;
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT REFERENCES ON TABLES    TO dbrole_admin;
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT TRIGGER    ON TABLES    TO dbrole_admin;
ALTER DEFAULT PRIVILEGES FOR ROLE "{{ database.owner }}" GRANT CREATE     ON SCHEMAS   TO dbrole_admin;
{% endif %}
{% endif %}


--==================================================================--
--                          CREATE SCHEMAS                          --
--==================================================================--
-- create schemas
{% if 'schemas' in database and database.schemas is not none and database.schemas|length > 0 %}{% for schema in database.schemas %}
{% if schema is string %}
CREATE SCHEMA IF NOT EXISTS "{{ schema }}";
{% elif schema is mapping and 'name' in schema %}
{% if schema.state is defined and schema.state == 'absent' %}
DROP SCHEMA IF EXISTS "{{ schema.name }}" CASCADE;
{% else %}
CREATE SCHEMA IF NOT EXISTS "{{ schema.name }}"{% if 'owner' in schema and schema.owner is not none and schema.owner != '' %} AUTHORIZATION "{{ schema.owner }}"{% endif %};
{% endif %}
{% endif %}
{% endfor %}{% endif %}


--==================================================================--
--                        CREATE EXTENSIONS                        --
--==================================================================--
-- create extensions
{% if 'extensions' in database and database.extensions is not none and database.extensions|length > 0 %}{% for extension in database.extensions %}
{% if extension is string %}
CREATE EXTENSION IF NOT EXISTS "{{ extension }}" CASCADE;
{% elif extension is mapping and 'name' in extension %}
{% if extension.state is defined and extension.state == 'absent' %}
DROP EXTENSION IF EXISTS "{{ extension.name }}" CASCADE;
{% else %}
CREATE EXTENSION IF NOT EXISTS "{{ extension.name }}"{% if 'schema' in extension %} WITH SCHEMA "{{ extension.schema }}"{% endif %}{% if 'version' in extension %} VERSION '{{ extension.version }}'{% endif %} CASCADE;
{% endif %}
{% endif %}
{% endfor %}{% endif %}


--==================================================================--
--                        PGBOUNCER DATABASE                        --
--==================================================================--
-- database will be added to pgbouncer database list by default,
-- unless pgbouncer is explicitly set to 'false', means hidden database

{% if 'pgbouncer' not in database or database.pgbouncer|bool == true %}
-- Database '{{ database.name }}' will be added to /etc/pgbouncer/database.txt via
--
-- foreach database created on pgbouncer, add the function to retrieve
-- auth user passwords from pg_authid when auth_query is set to 'true'
-- The user designated for this purpose is {{ pg_monitor_username|default('dbuser_monitor') }}
{% else %}
-- Database '{{ database.name }}' will NOT be added to /etc/pgbouncer/database.txt
{% endif %}
--==================================================================--
