diff --git a/packages/migra/src/changes.ts b/packages/migra/src/changes.ts index a42892e4..c5a6d83c 100644 --- a/packages/migra/src/changes.ts +++ b/packages/migra/src/changes.ts @@ -5,7 +5,7 @@ import * as schemainspect from '@pgkit/schemainspect' import {asa, isa, BaseInspectedSelectable, pg} from '@pgkit/schemainspect' import {Statements} from './statements' -import {sortKeys, differences} from './util' +import {sortKeys, differences, isEqual} from './util' const {InspectedConstraint, InspectedEnum, InspectedExtension} = pg @@ -398,6 +398,17 @@ function get_table_changes({ const rls_alter = v.alter_rls_statement statements.append(rls_alter) } + + // Check for reloptions changes + // Sort arrays before comparison since order doesn't matter for reloptions + const before_opts = (before.reloptions || []).slice().sort() + const after_opts = (v.reloptions || []).slice().sort() + if (!isEqual(before_opts, after_opts) && v.is_table) { + const alter_reloptions = v.alter_reloptions_statement + if (alter_reloptions) { + statements.append(alter_reloptions) + } + } } // const [seq_created, seq_dropped, seq_modified, _] = differences(sequences_from, sequences_target) diff --git a/packages/migra/test/NEW_FIXTURES/viewreloptions/a.sql b/packages/migra/test/NEW_FIXTURES/viewreloptions/a.sql new file mode 100644 index 00000000..8ee77ec2 --- /dev/null +++ b/packages/migra/test/NEW_FIXTURES/viewreloptions/a.sql @@ -0,0 +1,12 @@ +create table test_table(id serial primary key, name text); + +create table test_table_with_options(id serial primary key, name text) with (fillfactor = 80); + +create view test_view_without_options as select name from test_table; + +create view test_view_with_options with (security_barrier = true) as select name from test_table; + +create materialized view test_matview_without_options as select name from test_table; + +create materialized view test_matview_with_options with (fillfactor = 90, autovacuum_enabled = false) as select name from test_table; + diff --git a/packages/migra/test/NEW_FIXTURES/viewreloptions/additions.sql b/packages/migra/test/NEW_FIXTURES/viewreloptions/additions.sql new file mode 100644 index 00000000..e69de29b diff --git a/packages/migra/test/NEW_FIXTURES/viewreloptions/b.sql b/packages/migra/test/NEW_FIXTURES/viewreloptions/b.sql new file mode 100644 index 00000000..4f0aba97 --- /dev/null +++ b/packages/migra/test/NEW_FIXTURES/viewreloptions/b.sql @@ -0,0 +1,24 @@ +create table test_table(id serial primary key, name text); + +-- Same table with different reloptions +create table test_table_with_options(id serial primary key, name text) with (fillfactor = 70, parallel_workers = 4); + +-- Add a new table with reloptions +create table test_table_new_with_options(id serial primary key, name text) with (fillfactor = 75, autovacuum_enabled = false); + +create view test_view_without_options as select name from test_table; + +-- Same view with different reloptions +create view test_view_with_options with (security_barrier = false) as select name from test_table; + +-- Add a new view with reloptions +create view test_view_new_with_options with (security_barrier = true) as select id from test_table; + +create materialized view test_matview_without_options as select name from test_table; + +-- Same materialized view with different reloptions +create materialized view test_matview_with_options with (fillfactor = 85, autovacuum_enabled = true) as select name from test_table; + +-- Add a new materialized view with reloptions +create materialized view test_matview_new_with_options with (parallel_workers = 2) as select id from test_table; + diff --git a/packages/migra/test/NEW_FIXTURES/viewreloptions/expected.sql b/packages/migra/test/NEW_FIXTURES/viewreloptions/expected.sql new file mode 100644 index 00000000..b2b6e371 --- /dev/null +++ b/packages/migra/test/NEW_FIXTURES/viewreloptions/expected.sql @@ -0,0 +1,29 @@ +create sequence "public"."test_table_new_with_options_id_seq"; + +drop materialized view if exists "public"."test_matview_with_options"; + +create table "public"."test_table_new_with_options" ( + "id" integer not null default nextval('test_table_new_with_options_id_seq'::regclass), + "name" text +) with (fillfactor = 75, autovacuum_enabled = false); + +alter table "public"."test_table_with_options" set (fillfactor = 70, parallel_workers = 4); + +alter sequence "public"."test_table_new_with_options_id_seq" owned by "public"."test_table_new_with_options"."id"; + +CREATE UNIQUE INDEX test_table_new_with_options_pkey ON public.test_table_new_with_options USING btree (id); + +alter table "public"."test_table_new_with_options" add constraint "test_table_new_with_options_pkey" PRIMARY KEY using index "test_table_new_with_options_pkey"; + +create materialized view "public"."test_matview_new_with_options" with (parallel_workers = 2) as SELECT test_table.id + FROM test_table; + +create or replace view "public"."test_view_new_with_options" with (security_barrier = true) as SELECT test_table.id + FROM test_table; + +create materialized view "public"."test_matview_with_options" with (fillfactor = 85, autovacuum_enabled = true) as SELECT test_table.name + FROM test_table; + +create or replace view "public"."test_view_with_options" with (security_barrier = false) as SELECT test_table.name + FROM test_table; + diff --git a/packages/migra/test/NEW_FIXTURES/viewreloptions/expected2.sql b/packages/migra/test/NEW_FIXTURES/viewreloptions/expected2.sql new file mode 100644 index 00000000..e69de29b diff --git a/packages/schemainspect/queries/pg/sql/relations.sql b/packages/schemainspect/queries/pg/sql/relations.sql index 92f5f94f..28339000 100644 --- a/packages/schemainspect/queries/pg/sql/relations.sql +++ b/packages/schemainspect/queries/pg/sql/relations.sql @@ -52,7 +52,8 @@ r as ( c.relforcerowsecurity::boolean as forcerowsecurity, c.relpersistence as persistence, c.relpages as page_size_estimate, - c.reltuples as row_count_estimate + c.reltuples as row_count_estimate, + c.reloptions as reloptions from pg_catalog.pg_class c inner join pg_catalog.pg_namespace n @@ -92,7 +93,8 @@ select r.forcerowsecurity, r.persistence, r.page_size_estimate, - r.row_count_estimate + r.row_count_estimate, + r.reloptions FROM r left join pg_catalog.pg_attribute a diff --git a/packages/schemainspect/src/inspected.ts b/packages/schemainspect/src/inspected.ts index 38155d70..98d985a3 100644 --- a/packages/schemainspect/src/inspected.ts +++ b/packages/schemainspect/src/inspected.ts @@ -274,6 +274,7 @@ export interface BaseInspectedSelectableOptions { rowsecurity?: boolean forcerowsecurity?: boolean persistence?: any + reloptions?: string[] | null } export abstract class BaseInspectedSelectable extends AutoThisAssigner< @@ -311,6 +312,7 @@ export abstract class BaseInspectedSelectable extends AutoThisAssigner< 'partition_def', 'rowsecurity', 'persistence', + 'reloptions', ]) } } diff --git a/packages/schemainspect/src/pg/obj.ts b/packages/schemainspect/src/pg/obj.ts index b8c556a8..28efb2f7 100644 --- a/packages/schemainspect/src/pg/obj.ts +++ b/packages/schemainspect/src/pg/obj.ts @@ -38,7 +38,7 @@ const resource_text = (relativePath: string): string => { const CREATE_TABLE = ` create {}table {} ({} - ){}{}; + ){}{}{}; ` const CREATE_TABLE_SUBCLASS = ` create {}table {} partition of {} {}; @@ -153,6 +153,10 @@ export class InspectedSelectable extends BaseInspectedSelectable { get create_statement(): string { const n = this.quoted_full_name let create_statement: string + let reloptions_clause = '' + if (this.reloptions && this.reloptions.length > 0) { + reloptions_clause = ` with (${this.reloptions.join(', ')})` + } if (['r', 'p'].includes(this.relationtype)) { if (this.is_partitioning_child_table) { @@ -180,18 +184,18 @@ export class InspectedSelectable extends BaseInspectedSelectable { inherits_clause = ` inherits (${this.parent_table})` } - create_statement = format(CREATE_TABLE, this.persistence_modifier, n, colspec, partition_key, inherits_clause) + create_statement = format(CREATE_TABLE, this.persistence_modifier, n, colspec, partition_key, inherits_clause, reloptions_clause) } } else switch (this.relationtype) { case 'v': { - create_statement = `create or replace view ${n} as ${this.definition}\n` + create_statement = `create or replace view ${n}${reloptions_clause} as ${this.definition}\n` break } case 'm': { - create_statement = `create materialized view ${n} as ${this.definition}\n` + create_statement = `create materialized view ${n}${reloptions_clause} as ${this.definition}\n` break } @@ -342,6 +346,24 @@ export class InspectedSelectable extends BaseInspectedSelectable { const keyword = this.is_unlogged ? 'unlogged' : 'logged' return this.alter_table_statement(`set ${keyword}`) } + + get alter_reloptions_clause(): string | null { + if (this.reloptions && this.reloptions.length > 0) { + return `set (${this.reloptions.join(', ')})` + } + return null + } + + get alter_reloptions_statement(): string | null { + if (!this.is_alterable) { + return null + } + const clause = this.alter_reloptions_clause + if (clause) { + return this.alter_table_statement(clause) + } + return null + } } export interface InspectedFunctionParams { @@ -1603,6 +1625,7 @@ export class PostgreSQL extends DBInspector { rowsecurity: f.rowsecurity, forcerowsecurity: f.forcerowsecurity, persistence: f.persistence, + reloptions: f.reloptions, }) // const RELATIONTYPES = { diff --git a/packages/schemainspect/src/queries.ts b/packages/schemainspect/src/queries.ts index d689f212..80b5a8e8 100644 --- a/packages/schemainspect/src/queries.ts +++ b/packages/schemainspect/src/queries.ts @@ -33,7 +33,7 @@ export const queries = { "functions.sql": "with extension_oids as (\n select\n objid\n from\n pg_depend d\n WHERE\n d.refclassid = 'pg_extension'::regclass\n and d.classid = 'pg_proc'::regclass\n ),\n pg_proc_pre as (\n select\n pp.*,\n -- 11_AND_LATER pp.oid as p_oid\n -- 10_AND_EARLIER pp.oid as p_oid, case when pp.proisagg then 'a' else 'f' end as prokind\n from pg_proc pp\n ),\nroutines as (\n SELECT current_database()::information_schema.sql_identifier AS specific_catalog,\n n.nspname::information_schema.sql_identifier AS specific_schema,\n --nameconcatoid(p.proname, p.oid)::information_schema.sql_identifier AS specific_name,\n current_database()::information_schema.sql_identifier AS routine_catalog,\n n.nspname::information_schema.sql_identifier AS schema,\n p.proname::information_schema.sql_identifier AS name,\n CASE p.prokind\n WHEN 'f'::\"char\" THEN 'FUNCTION'::text\n WHEN 'p'::\"char\" THEN 'PROCEDURE'::text\n ELSE NULL::text\n END::information_schema.character_data AS routine_type,\n CASE\n WHEN p.prokind = 'p'::\"char\" THEN NULL::text\n WHEN t.typelem <> 0::oid AND t.typlen = '-1'::integer THEN 'ARRAY'::text\n WHEN nt.nspname = 'pg_catalog'::name THEN format_type(t.oid, NULL::integer)\n ELSE 'USER-DEFINED'::text\n END::information_schema.character_data AS data_type,\n\n CASE\n WHEN nt.nspname IS NOT NULL THEN current_database()\n ELSE NULL::name\n END::information_schema.sql_identifier AS type_udt_catalog,\n nt.nspname::information_schema.sql_identifier AS type_udt_schema,\n t.typname::information_schema.sql_identifier AS type_udt_name,\n CASE\n WHEN p.prokind <> 'p'::\"char\" THEN 0\n ELSE NULL::integer\n END::information_schema.sql_identifier AS dtd_identifier,\n CASE\n WHEN l.lanname = 'sql'::name THEN 'SQL'::text\n ELSE 'EXTERNAL'::text\n END::information_schema.character_data AS routine_body,\n CASE\n WHEN pg_has_role(p.proowner, 'USAGE'::text) THEN p.prosrc\n ELSE NULL::text\n END::information_schema.character_data AS definition,\n CASE\n WHEN l.lanname = 'c'::name THEN p.prosrc\n ELSE NULL::text\n END::information_schema.character_data AS external_name,\n upper(l.lanname::text)::information_schema.character_data AS external_language,\n 'GENERAL'::character varying::information_schema.character_data AS parameter_style,\n CASE\n WHEN p.provolatile = 'i'::\"char\" THEN 'YES'::text\n ELSE 'NO'::text\n END::information_schema.yes_or_no AS is_deterministic,\n 'MODIFIES'::character varying::information_schema.character_data AS sql_data_access,\n CASE\n WHEN p.prokind <> 'p'::\"char\" THEN\n CASE\n WHEN p.proisstrict THEN 'YES'::text\n ELSE 'NO'::text\n END\n ELSE NULL::text\n END::information_schema.yes_or_no AS is_null_call,\n 'YES'::character varying::information_schema.yes_or_no AS schema_level_routine,\n 0::information_schema.cardinal_number AS max_dynamic_result_sets,\n CASE\n WHEN p.prosecdef THEN 'DEFINER'::text\n ELSE 'INVOKER'::text\n END::information_schema.character_data AS security_type,\n 'NO'::character varying::information_schema.yes_or_no AS as_locator,\n 'NO'::character varying::information_schema.yes_or_no AS is_udt_dependent,\n p.p_oid as oid,\n p.proisstrict,\n p.prosecdef,\n p.provolatile,\n p.proargtypes,\n p.proallargtypes,\n p.proargnames,\n p.proargdefaults,\n p.proargmodes,\n p.proowner,\n p.prokind as kind\n FROM pg_namespace n\n JOIN pg_proc_pre p ON n.oid = p.pronamespace\n JOIN pg_language l ON p.prolang = l.oid\n LEFT JOIN (pg_type t\n JOIN pg_namespace nt ON t.typnamespace = nt.oid) ON p.prorettype = t.oid AND p.prokind <> 'p'::\"char\"\n WHERE pg_has_role(p.proowner, 'USAGE'::text) OR has_function_privilege(p.p_oid, 'EXECUTE'::text)\n\n),\n pgproc as (\n select\n schema,\n name,\n p.oid as oid,\n e.objid as extension_oid,\n case proisstrict when true then\n 'RETURNS NULL ON NULL INPUT'\n else\n 'CALLED ON NULL INPUT'\n end as strictness,\n case prosecdef when true then\n 'SECURITY DEFINER'\n else\n 'SECURITY INVOKER'\n end as security_type,\n case provolatile\n when 'i' then\n 'IMMUTABLE'\n when 's' then\n 'STABLE'\n when 'v' then\n 'VOLATILE'\n else\n null\n end as volatility,\n p.proargtypes,\n p.proallargtypes,\n p.proargnames,\n p.proargdefaults,\n p.proargmodes,\n p.proowner,\n COALESCE(p.proallargtypes, p.proargtypes::oid[]) as procombinedargtypes,\n p.kind,\n p.type_udt_schema,\n p.type_udt_name,\n p.definition,\n p.external_language\n\n from\n routines p\n left outer join extension_oids e\n on p.oid = e.objid\n where true\n -- 11_AND_LATER and p.kind != 'a'\n -- SKIP_INTERNAL and schema not in ('pg_internal', 'pg_catalog', 'information_schema', 'pg_toast')\n -- SKIP_INTERNAL and schema not like 'pg_temp_%' and schema not like 'pg_toast_temp_%'\n -- SKIP_INTERNAL and e.objid is null\n -- SKIP_INTERNAL and p.external_language not in ('C', 'INTERNAL')\n ),\nunnested as (\n select\n p.*,\n pname as parameter_name,\n pnum as position_number,\n CASE\n WHEN pargmode IS NULL THEN null\n WHEN pargmode = 'i'::\"char\" THEN 'IN'::text\n WHEN pargmode = 'o'::\"char\" THEN 'OUT'::text\n WHEN pargmode = 'b'::\"char\" THEN 'INOUT'::text\n WHEN pargmode = 'v'::\"char\" THEN 'IN'::text\n WHEN pargmode = 't'::\"char\" THEN 'OUT'::text\n ELSE NULL::text\n END::information_schema.character_data AS parameter_mode,\n CASE\n WHEN t.typelem <> 0::oid AND t.typlen = '-1'::integer THEN 'ARRAY'::text\n else format_type(t.oid, NULL::integer)\n\n END::information_schema.character_data AS data_type,\n CASE\n WHEN pg_has_role(p.proowner, 'USAGE'::text) THEN pg_get_function_arg_default(p.oid, pnum::int)\n ELSE NULL::text\n END::varchar AS parameter_default\n from pgproc p\n left join lateral\n unnest(\n p.proargnames,\n p.proallargtypes,\n p.procombinedargtypes,\n p.proargmodes)\n WITH ORDINALITY AS uu(pname, pdatatype, pargtype, pargmode, pnum) ON TRUE\n left join pg_type t\n on t.oid = uu.pargtype\n),\n pre as (\n SELECT\n p.schema as schema,\n p.name as name,\n case when p.data_type = 'USER-DEFINED' then\n '\"' || p.type_udt_schema || '\".\"' || p.type_udt_name || '\"'\n else\n p.data_type\n end as returntype,\n p.data_type = 'USER-DEFINED' as has_user_defined_returntype,\n p.parameter_name as parameter_name,\n p.data_type as data_type,\n p.parameter_mode as parameter_mode,\n p.parameter_default as parameter_default,\n p.position_number as position_number,\n p.definition as definition,\n pg_get_functiondef(p.oid) as full_definition,\n p.external_language as language,\n p.strictness as strictness,\n p.security_type as security_type,\n p.volatility as volatility,\n p.kind as kind,\n p.oid as oid,\n p.extension_oid as extension_oid,\n pg_get_function_result(p.oid) as result_string,\n pg_get_function_identity_arguments(p.oid) as identity_arguments,\n pg_catalog.obj_description(p.oid) as comment\n FROM\n unnested p\n )\nselect\n*\nfrom pre\norder by\n schema, name, parameter_mode, position_number, parameter_name;\n", "indexes.sql": "with extension_oids as (\n select\n objid,\n classid::regclass::text as classid\n from\n pg_depend d\n WHERE\n d.refclassid = 'pg_extension'::regclass and\n d.classid = 'pg_index'::regclass\n),\nextension_relations as (\n select\n objid\n from\n pg_depend d\n WHERE\n d.refclassid = 'pg_extension'::regclass and\n d.classid = 'pg_class'::regclass\n), pre as (\n SELECT n.nspname AS schema,\n c.relname AS table_name,\n i.relname AS name,\n i.oid as oid,\n e.objid as extension_oid,\n pg_get_indexdef(i.oid) AS definition,\n (\n select\n array_agg(attname order by ik.n)\n from\n unnest(x.indkey) with ordinality ik(i, n)\n join pg_attribute aa\n on\n aa.attrelid = x.indrelid\n and ik.i = aa.attnum\n )\n index_columns,\n indoption key_options,\n indnatts total_column_count,\n -- 11_AND_LATER indnkeyatts key_column_count,\n -- 10_AND_EARLIER indnatts key_column_count,\n indnatts num_att,\n -- 11_AND_LATER indnatts - indnkeyatts included_column_count,\n -- 10_AND_EARLIER 0 included_column_count,\n indisunique is_unique,\n indisprimary is_pk,\n indisexclusion is_exclusion,\n indimmediate is_immediate,\n indisclustered is_clustered,\n indcollation key_collations,\n pg_get_expr(indexprs, indrelid) key_expressions,\n pg_get_expr(indpred, indrelid) partial_predicate,\n amname algorithm\n FROM pg_index x\n JOIN pg_class c ON c.oid = x.indrelid\n JOIN pg_class i ON i.oid = x.indexrelid\n JOIN pg_am am ON i.relam = am.oid\n LEFT JOIN pg_namespace n ON n.oid = c.relnamespace\n left join extension_oids e\n on i.oid = e.objid\n left join extension_relations er\n on c.oid = er.objid\nWHERE\n x.indislive\n and c.relkind in ('r', 'm', 'p') AND i.relkind in ('i', 'I')\n -- SKIP_INTERNAL and nspname not in ('pg_catalog', 'information_schema', 'pg_toast')\n -- SKIP_INTERNAL and nspname not like 'pg_temp_%' and nspname not like 'pg_toast_temp_%'\n -- SKIP_INTERNAL and e.objid is null and er.objid is null\n)\nselect * ,\nindex_columns[1\\:key_column_count] as key_columns,\nindex_columns[key_column_count+1\\:array_length(index_columns, 1)] as included_columns\nfrom pre\norder by 1, 2, 3;\n", "privileges.sql": "select\n table_schema as schema,\n table_name as name,\n 'table' as object_type,\n grantee as user,\n privilege_type as privilege\nfrom information_schema.role_table_grants\nwhere grantee != (\n select tableowner\n from pg_tables\n where schemaname = table_schema\n and tablename = table_name\n)\n-- SKIP_INTERNAL and table_schema not in ('pg_internal', 'pg_catalog', 'information_schema', 'pg_toast')\n-- SKIP_INTERNAL and table_schema not like 'pg_temp_%' and table_schema not like 'pg_toast_temp_%'\norder by schema, name, user;\n", - "relations.sql": "with extension_oids as (\n select\n objid\n from\n pg_depend d\n WHERE\n d.refclassid = 'pg_extension'::regclass and\n d.classid = 'pg_class'::regclass\n), enums as (\n\n SELECT\n t.oid as enum_oid,\n n.nspname as \"schema\",\n t.typname as name\n FROM pg_catalog.pg_type t\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace\n left outer join extension_oids e\n on t.oid = e.objid\n WHERE\n t.typcategory = 'E'\n and e.objid is null\n -- SKIP_INTERNAL and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')\n -- SKIP_INTERNAL and n.nspname not like 'pg_temp_%' and n.nspname not like 'pg_toast_temp_%'\n ORDER BY 1, 2\n),\nr as (\n select\n c.relname as name,\n n.nspname as schema,\n c.relkind as relationtype,\n c.oid as oid,\n case when c.relkind in ('m', 'v') then\n pg_get_viewdef(c.oid)\n else null end\n as definition,\n (SELECT\n '\"' || nmsp_parent.nspname || '\".\"' || parent.relname || '\"' as parent\n FROM pg_inherits\n JOIN pg_class parent ON pg_inherits.inhparent = parent.oid\n JOIN pg_class child ON pg_inherits.inhrelid = child.oid\n JOIN pg_namespace nmsp_parent ON nmsp_parent.oid = parent.relnamespace\n JOIN pg_namespace nmsp_child ON nmsp_child.oid = child.relnamespace\n where child.oid = c.oid)\n as parent_table,\n case when c.relpartbound is not null then\n pg_get_expr(c.relpartbound, c.oid, true)\n when c.relhassubclass is not null then\n pg_catalog.pg_get_partkeydef(c.oid)\n end\n as partition_def,\n c.relrowsecurity::boolean as rowsecurity,\n c.relforcerowsecurity::boolean as forcerowsecurity,\n c.relpersistence as persistence,\n c.relpages as page_size_estimate,\n c.reltuples as row_count_estimate\n from\n pg_catalog.pg_class c\n inner join pg_catalog.pg_namespace n\n ON n.oid = c.relnamespace\n left outer join extension_oids e\n on c.oid = e.objid\n where c.relkind in ('r', 'v', 'm', 'c', 'p')\n -- SKIP_INTERNAL and e.objid is null\n -- SKIP_INTERNAL and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')\n -- SKIP_INTERNAL and n.nspname not like 'pg_temp_%' and n.nspname not like 'pg_toast_temp_%'\n)\nselect\n r.relationtype,\n r.schema,\n r.name,\n r.definition as definition,\n a.attnum as position_number,\n a.attname as attname,\n a.attnotnull as not_null,\n a.atttypid::regtype AS datatype,\n a.attidentity != '' as is_identity,\n a.attidentity = 'a' as is_identity_always,\n -- PRE_12 false as is_generated,\n -- 12_ONLY a.attgenerated != '' as is_generated,\n (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t\n WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) AS collation,\n pg_get_expr(ad.adbin, ad.adrelid) as defaultdef,\n r.oid as oid,\n format_type(atttypid, atttypmod) AS datatypestring,\n e.enum_oid is not null as is_enum,\n e.name as enum_name,\n e.schema as enum_schema,\n pg_catalog.obj_description(r.oid) as comment,\n r.parent_table,\n r.partition_def,\n r.rowsecurity,\n r.forcerowsecurity,\n r.persistence,\n r.page_size_estimate,\n r.row_count_estimate\nFROM\n r\n left join pg_catalog.pg_attribute a\n on r.oid = a.attrelid and a.attnum > 0\n left join pg_catalog.pg_attrdef ad\n on a.attrelid = ad.adrelid\n and a.attnum = ad.adnum\n left join enums e\n on a.atttypid = e.enum_oid\nwhere a.attisdropped is not true\n-- SKIP_INTERNAL and r.schema not in ('pg_catalog', 'information_schema', 'pg_toast')\n-- SKIP_INTERNAL and r.schema not like 'pg_temp_%' and r.schema not like 'pg_toast_temp_%'\norder by relationtype, r.schema, r.name, position_number;\n", + "relations.sql": "with extension_oids as (\n select\n objid\n from\n pg_depend d\n WHERE\n d.refclassid = 'pg_extension'::regclass and\n d.classid = 'pg_class'::regclass\n), enums as (\n\n SELECT\n t.oid as enum_oid,\n n.nspname as \"schema\",\n t.typname as name\n FROM pg_catalog.pg_type t\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace\n left outer join extension_oids e\n on t.oid = e.objid\n WHERE\n t.typcategory = 'E'\n and e.objid is null\n -- SKIP_INTERNAL and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')\n -- SKIP_INTERNAL and n.nspname not like 'pg_temp_%' and n.nspname not like 'pg_toast_temp_%'\n ORDER BY 1, 2\n),\nr as (\n select\n c.relname as name,\n n.nspname as schema,\n c.relkind as relationtype,\n c.oid as oid,\n case when c.relkind in ('m', 'v') then\n pg_get_viewdef(c.oid)\n else null end\n as definition,\n (SELECT\n '\"' || nmsp_parent.nspname || '\".\"' || parent.relname || '\"' as parent\n FROM pg_inherits\n JOIN pg_class parent ON pg_inherits.inhparent = parent.oid\n JOIN pg_class child ON pg_inherits.inhrelid = child.oid\n JOIN pg_namespace nmsp_parent ON nmsp_parent.oid = parent.relnamespace\n JOIN pg_namespace nmsp_child ON nmsp_child.oid = child.relnamespace\n where child.oid = c.oid)\n as parent_table,\n case when c.relpartbound is not null then\n pg_get_expr(c.relpartbound, c.oid, true)\n when c.relhassubclass is not null then\n pg_catalog.pg_get_partkeydef(c.oid)\n end\n as partition_def,\n c.relrowsecurity::boolean as rowsecurity,\n c.relforcerowsecurity::boolean as forcerowsecurity,\n c.relpersistence as persistence,\n c.relpages as page_size_estimate,\n c.reltuples as row_count_estimate,\n c.reloptions as reloptions\n from\n pg_catalog.pg_class c\n inner join pg_catalog.pg_namespace n\n ON n.oid = c.relnamespace\n left outer join extension_oids e\n on c.oid = e.objid\n where c.relkind in ('r', 'v', 'm', 'c', 'p')\n -- SKIP_INTERNAL and e.objid is null\n -- SKIP_INTERNAL and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')\n -- SKIP_INTERNAL and n.nspname not like 'pg_temp_%' and n.nspname not like 'pg_toast_temp_%'\n)\nselect\n r.relationtype,\n r.schema,\n r.name,\n r.definition as definition,\n a.attnum as position_number,\n a.attname as attname,\n a.attnotnull as not_null,\n a.atttypid::regtype AS datatype,\n a.attidentity != '' as is_identity,\n a.attidentity = 'a' as is_identity_always,\n -- PRE_12 false as is_generated,\n -- 12_ONLY a.attgenerated != '' as is_generated,\n (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t\n WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) AS collation,\n pg_get_expr(ad.adbin, ad.adrelid) as defaultdef,\n r.oid as oid,\n format_type(atttypid, atttypmod) AS datatypestring,\n e.enum_oid is not null as is_enum,\n e.name as enum_name,\n e.schema as enum_schema,\n pg_catalog.obj_description(r.oid) as comment,\n r.parent_table,\n r.partition_def,\n r.rowsecurity,\n r.forcerowsecurity,\n r.persistence,\n r.page_size_estimate,\n r.row_count_estimate,\n r.reloptions\nFROM\n r\n left join pg_catalog.pg_attribute a\n on r.oid = a.attrelid and a.attnum > 0\n left join pg_catalog.pg_attrdef ad\n on a.attrelid = ad.adrelid\n and a.attnum = ad.adnum\n left join enums e\n on a.atttypid = e.enum_oid\nwhere a.attisdropped is not true\n-- SKIP_INTERNAL and r.schema not in ('pg_catalog', 'information_schema', 'pg_toast')\n-- SKIP_INTERNAL and r.schema not like 'pg_temp_%' and r.schema not like 'pg_toast_temp_%'\norder by relationtype, r.schema, r.name, position_number;\n", "relations9.sql": "with extension_oids as (\n select\n objid\n from\n pg_depend d\n WHERE\n d.refclassid = 'pg_extension'::regclass\n), enums as (\n\n SELECT\n t.oid as enum_oid,\n n.nspname as \"schema\",\n pg_catalog.format_type(t.oid, NULL) AS \"name\"\n FROM pg_catalog.pg_type t\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace\n left outer join extension_oids e\n on t.oid = e.objid\n WHERE\n t.typcategory = 'E'\n and e.objid is null\n -- SKIP_INTERNAL and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')\n -- SKIP_INTERNAL and n.nspname not like 'pg_temp_%' and n.nspname not like 'pg_toast_temp_%'\n AND pg_catalog.pg_type_is_visible(t.oid)\n ORDER BY 1, 2\n),\nr as (\n select\n c.relname as name,\n n.nspname as schema,\n c.relkind as relationtype,\n c.oid as oid,\n case when c.relkind in ('m', 'v') then\n pg_get_viewdef(c.oid)\n else null end\n as definition,\n null\n as parent_table,\n null as partition_def,\n c.relrowsecurity::boolean as rowsecurity,\n c.relforcerowsecurity::boolean as forcerowsecurity,\n c.relpersistence as persistence,\n c.relpages as page_size_estimate,\n c.reltuples as row_count_estimate\n from\n pg_catalog.pg_class c\n inner join pg_catalog.pg_namespace n\n ON n.oid = c.relnamespace\n left outer join extension_oids e\n on c.oid = e.objid\n where c.relkind in ('r', 'v', 'm', 'c', 'p')\n -- SKIP_INTERNAL and e.objid is null\n -- SKIP_INTERNAL and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')\n -- SKIP_INTERNAL and n.nspname not like 'pg_temp_%' and n.nspname not like 'pg_toast_temp_%'\n)\nselect\n r.relationtype,\n r.schema,\n r.name,\n r.definition as definition,\n a.attnum as position_number,\n a.attname as attname,\n a.attnotnull as not_null,\n a.atttypid::regtype AS datatype,\n false AS is_identity,\n false as is_identity_always,\n false as is_generated,\n (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t\n WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) AS collation,\n pg_get_expr(ad.adbin, ad.adrelid) as defaultdef,\n r.oid as oid,\n format_type(atttypid, atttypmod) AS datatypestring,\n e.enum_oid is not null as is_enum,\n e.name as enum_name,\n e.schema as enum_schema,\n pg_catalog.obj_description(r.oid) as comment,\n r.parent_table,\n r.partition_def,\n r.rowsecurity,\n r.forcerowsecurity,\n r.persistence,\n r.page_size_estimate,\n r.row_count_estimate\nFROM\n r\n left join pg_catalog.pg_attribute a\n on r.oid = a.attrelid and a.attnum > 0\n left join pg_catalog.pg_attrdef ad\n on a.attrelid = ad.adrelid\n and a.attnum = ad.adnum\n left join enums e\n on a.atttypid = e.enum_oid\nwhere a.attisdropped is not true\n-- SKIP_INTERNAL and r.schema not in ('pg_catalog', 'information_schema', 'pg_toast')\n-- SKIP_INTERNAL and r.schema not like 'pg_temp_%' and r.schema not like 'pg_toast_temp_%'\norder by relationtype, r.schema, r.name, position_number;\n", "rlspolicies.sql": "select\n p.polname as name,\n n.nspname as schema,\n c.relname as table_name,\n p.polcmd as commandtype,\n p.polpermissive as permissive,\n (\n select\n array_agg(\n case when o = 0 THEN\n 'public'\n else\n pg_get_userbyid(o)\n end\n )\n from\n unnest(p.polroles) as unn(o)\n )\n as roles,\n p.polqual as qualtree,\n pg_get_expr(p.polqual, p.polrelid) as qual,\n pg_get_expr(p.polwithcheck, p.polrelid) as withcheck\nfrom\n pg_policy p\n join pg_class c ON c.oid = p.polrelid\n JOIN pg_namespace n ON n.oid = c.relnamespace\norder by\n 2, 1\n", "schemas.sql": "with extension_oids as (\n select\n objid\n from\n pg_depend d\n WHERE\n d.refclassid = 'pg_extension'::regclass\n and d.classid = 'pg_namespace'::regclass\n) select\n nspname as schema\nfrom\n pg_catalog.pg_namespace\n left outer join extension_oids e\n \ton e.objid = oid\n-- SKIP_INTERNAL where nspname not in ('pg_internal', 'pg_catalog', 'information_schema', 'pg_toast')\n-- SKIP_INTERNAL and nspname not like 'pg_temp_%' and nspname not like 'pg_toast_temp_%'\n-- SKIP_INTERNAL and e.objid is null\norder by 1;\n", diff --git a/packages/schemainspect/src/types.ts b/packages/schemainspect/src/types.ts index 949aa116..9998f0b0 100644 --- a/packages/schemainspect/src/types.ts +++ b/packages/schemainspect/src/types.ts @@ -46,6 +46,7 @@ export interface AllRelationsQuery { persistence: Persistence; page_size_estimate: number; row_count_estimate: number; + reloptions: string[] | null; } export type Datatype = "text" | "integer" | "circle" | "numeric" | "uuid" | "order_status" | "other.otherenum1" | "other.otherenum2" | "e" | "character varying" | "shipping_status" | "usage_dropped_enum" | "hstore" | "interval" | "bigint" | "timestamp without time zone" | "date" | "character varying(10)";