From f02904a959aec0bf7f6edf129a90453994891b3b Mon Sep 17 00:00:00 2001 From: Remo <59358383+monacoremo@users.noreply.github.com> Date: Wed, 29 Apr 2020 20:01:46 +0200 Subject: [PATCH] Simplify the DbStructure queries (#1478) * remove unused parts of the DbStructure queries * explain magic numbers in DbStructure queries * simplify primary keys query --- src/PostgREST/DbStructure.hs | 276 ++++++++++++++--------------------- 1 file changed, 110 insertions(+), 166 deletions(-) diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index 720d157cf..1e23c3d29 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -189,14 +189,14 @@ accessibleProcs = H.Statement (toS sql) (param HE.text) decodeProcs True procsSqlQuery :: SqlQuery procsSqlQuery = [q| SELECT - pn.nspname as "proc_schema", - p.proname as "proc_name", - d.description as "proc_description", - pg_get_function_arguments(p.oid) as "args", - tn.nspname as "rettype_schema", - coalesce(comp.relname, t.typname) as "rettype_name", - p.proretset as "rettype_is_setof", - t.typtype as "rettype_typ", + pn.nspname as proc_schema, + p.proname as proc_name, + d.description as proc_description, + pg_get_function_arguments(p.oid) as args, + tn.nspname as rettype_schema, + coalesce(comp.relname, t.typname) as rettype_name, + p.proretset as rettype_is_setof, + t.typtype as rettype_typ, p.provolatile FROM pg_proc p JOIN pg_namespace pn ON pn.oid = p.pronamespace @@ -228,11 +228,22 @@ accessibleTables = n.nspname as table_schema, relname as table_name, d.description as table_description, - c.relkind = 'r' or (c.relkind IN ('v', 'f')) and (pg_relation_is_updatable(c.oid::regclass, false) & 8) = 8 - or (exists ( - select 1 - from pg_trigger - where pg_trigger.tgrelid = c.oid and (pg_trigger.tgtype::integer & 69) = 69) + ( + c.relkind in ('r', 'v', 'f') + and (pg_relation_is_updatable(c.oid::regclass, false) & 8) = 8 + -- The function `pg_relation_is_updateable` returns a bitmask where 8 + -- corresponds to `1 << CMD_INSERT` in the PostgreSQL source code, i.e. + -- it's possible to insert into the relation. + or (exists ( + select 1 + from pg_trigger + where + pg_trigger.tgrelid = c.oid + and (pg_trigger.tgtype::integer & 69) = 69) + -- The trigger type `tgtype` is a bitmask where 69 corresponds to + -- TRIGGER_TYPE_ROW + TRIGGER_TYPE_INSTEAD + TRIGGER_TYPE_INSERT + -- in the PostgreSQL source code. + ) ) as insertable from pg_class c @@ -242,9 +253,9 @@ accessibleTables = c.relkind in ('v', 'r', 'm', 'f') and n.nspname = $1 and ( - pg_has_role(c.relowner, 'USAGE'::text) - or has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'::text) - or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES'::text) + pg_has_role(c.relowner, 'USAGE') + or has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER') + or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES') ) order by relname |] @@ -373,13 +384,17 @@ allTables = n.nspname AS table_schema, c.relname AS table_name, NULL AS table_description, - c.relkind = 'r' OR (c.relkind IN ('v','f')) - AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 8) = 8 - OR (EXISTS - ( SELECT 1 + ( + c.relkind IN ('r', 'v','f') + AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 8) = 8 + OR EXISTS ( + SELECT 1 FROM pg_trigger - WHERE pg_trigger.tgrelid = c.oid - AND (pg_trigger.tgtype::integer & 69) = 69) ) AS insertable + WHERE + pg_trigger.tgrelid = c.oid + AND (pg_trigger.tgtype::integer & 69) = 69 + ) + ) AS insertable FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('v','r','m','f') @@ -406,9 +421,7 @@ allColumns tabs = info.column_default AS default_value, array_to_string(enum_info.vals, ',') AS enum FROM ( - /* -- CTE based on pg_catalog to get PRIMARY/FOREIGN key and UNIQUE columns outside api schema - */ WITH key_columns AS ( SELECT r.oid AS r_oid, @@ -436,19 +449,16 @@ allColumns tabs = -- limit columns to the ones in the api schema or PK/FK columns */ columns AS ( - SELECT current_database()::information_schema.sql_identifier AS table_catalog, - nc.nspname::information_schema.sql_identifier AS table_schema, - c.relname::information_schema.sql_identifier AS table_name, - a.attname::information_schema.sql_identifier AS column_name, + SELECT + nc.nspname::name AS table_schema, + c.relname::name AS table_name, + a.attname::name AS column_name, d.description AS description, - a.attnum::information_schema.cardinal_number AS ordinal_position, - pg_get_expr(ad.adbin, ad.adrelid)::information_schema.character_data AS column_default, + a.attnum::integer AS ordinal_position, + pg_get_expr(ad.adbin, ad.adrelid)::text AS column_default, + not (a.attnotnull OR t.typtype = 'd' AND t.typnotnull) AS is_nullable, CASE - WHEN a.attnotnull OR t.typtype = 'd'::"char" AND t.typnotnull THEN 'NO'::text - ELSE 'YES'::text - END::information_schema.yes_or_no AS is_nullable, - CASE - WHEN t.typtype = 'd'::"char" THEN + WHEN t.typtype = 'd' THEN CASE WHEN bt.typelem <> 0::oid AND bt.typlen = (-1) THEN 'ARRAY'::text WHEN nbt.nspname = 'pg_catalog'::name THEN format_type(t.typbasetype, NULL::integer) @@ -460,77 +470,42 @@ allColumns tabs = WHEN nt.nspname = 'pg_catalog'::name THEN format_type(a.atttypid, NULL::integer) ELSE format_type(a.atttypid, a.atttypmod) END - END::information_schema.character_data AS data_type, - information_schema._pg_char_max_length(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS character_maximum_length, - information_schema._pg_char_octet_length(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS character_octet_length, - information_schema._pg_numeric_precision(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS numeric_precision, - information_schema._pg_numeric_precision_radix(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS numeric_precision_radix, - information_schema._pg_numeric_scale(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS numeric_scale, - information_schema._pg_datetime_precision(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS datetime_precision, - information_schema._pg_interval_type(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.character_data AS interval_type, - NULL::integer::information_schema.cardinal_number AS interval_precision, - NULL::character varying::information_schema.sql_identifier AS character_set_catalog, - NULL::character varying::information_schema.sql_identifier AS character_set_schema, - NULL::character varying::information_schema.sql_identifier AS character_set_name, - CASE - WHEN nco.nspname IS NOT NULL THEN current_database() - ELSE NULL::name - END::information_schema.sql_identifier AS collation_catalog, - nco.nspname::information_schema.sql_identifier AS collation_schema, - co.collname::information_schema.sql_identifier AS collation_name, - CASE - WHEN t.typtype = 'd'::"char" THEN current_database() - ELSE NULL::name - END::information_schema.sql_identifier AS domain_catalog, - CASE - WHEN t.typtype = 'd'::"char" THEN nt.nspname - ELSE NULL::name - END::information_schema.sql_identifier AS domain_schema, - CASE - WHEN t.typtype = 'd'::"char" THEN t.typname - ELSE NULL::name - END::information_schema.sql_identifier AS domain_name, - current_database()::information_schema.sql_identifier AS udt_catalog, - COALESCE(nbt.nspname, nt.nspname)::information_schema.sql_identifier AS udt_schema, - COALESCE(bt.typname, t.typname)::information_schema.sql_identifier AS udt_name, - NULL::character varying::information_schema.sql_identifier AS scope_catalog, - NULL::character varying::information_schema.sql_identifier AS scope_schema, - NULL::character varying::information_schema.sql_identifier AS scope_name, - NULL::integer::information_schema.cardinal_number AS maximum_cardinality, - a.attnum::information_schema.sql_identifier AS dtd_identifier, - 'NO'::character varying::information_schema.yes_or_no AS is_self_referencing, - 'NO'::character varying::information_schema.yes_or_no AS is_identity, - NULL::character varying::information_schema.character_data AS identity_generation, - NULL::character varying::information_schema.character_data AS identity_start, - NULL::character varying::information_schema.character_data AS identity_increment, - NULL::character varying::information_schema.character_data AS identity_maximum, - NULL::character varying::information_schema.character_data AS identity_minimum, - NULL::character varying::information_schema.yes_or_no AS identity_cycle, - 'NEVER'::character varying::information_schema.character_data AS is_generated, - NULL::character varying::information_schema.character_data AS generation_expression, - CASE - WHEN c.relkind = 'r'::"char" OR (c.relkind = ANY (ARRAY['v'::"char", 'f'::"char"])) AND pg_column_is_updatable(c.oid::regclass, a.attnum, false) THEN 'YES'::text - ELSE 'NO'::text - END::information_schema.yes_or_no AS is_updatable + END::text AS data_type, + information_schema._pg_char_max_length( + information_schema._pg_truetypid(a.*, t.*), + information_schema._pg_truetypmod(a.*, t.*) + )::integer AS character_maximum_length, + information_schema._pg_numeric_precision( + information_schema._pg_truetypid(a.*, t.*), + information_schema._pg_truetypmod(a.*, t.*) + )::integer AS numeric_precision, + COALESCE(bt.typname, t.typname)::name AS udt_name, + ( + c.relkind in ('r', 'v', 'f') + AND pg_column_is_updatable(c.oid::regclass, a.attnum, false) + )::bool is_updatable FROM pg_attribute a - LEFT JOIN key_columns kc ON kc.conkey = a.attnum AND kc.c_oid = a.attrelid - LEFT JOIN pg_catalog.pg_description AS d ON d.objoid = a.attrelid and d.objsubid = a.attnum - LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum - JOIN (pg_class c - JOIN pg_namespace nc ON c.relnamespace = nc.oid) ON a.attrelid = c.oid - JOIN (pg_type t - JOIN pg_namespace nt ON t.typnamespace = nt.oid) ON a.atttypid = t.oid - LEFT JOIN (pg_type bt - JOIN pg_namespace nbt ON bt.typnamespace = nbt.oid) ON t.typtype = 'd'::"char" AND t.typbasetype = bt.oid - LEFT JOIN (pg_collation co - JOIN pg_namespace nco ON co.collnamespace = nco.oid) ON a.attcollation = co.oid AND (nco.nspname <> 'pg_catalog'::name OR co.collname <> 'default'::name) + LEFT JOIN key_columns kc + ON kc.conkey = a.attnum AND kc.c_oid = a.attrelid + LEFT JOIN pg_catalog.pg_description AS d + ON d.objoid = a.attrelid and d.objsubid = a.attnum + LEFT JOIN pg_attrdef ad + ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum + JOIN (pg_class c JOIN pg_namespace nc ON c.relnamespace = nc.oid) + ON a.attrelid = c.oid + JOIN (pg_type t JOIN pg_namespace nt ON t.typnamespace = nt.oid) + ON a.atttypid = t.oid + LEFT JOIN (pg_type bt JOIN pg_namespace nbt ON bt.typnamespace = nbt.oid) + ON t.typtype = 'd' AND t.typbasetype = bt.oid + LEFT JOIN (pg_collation co JOIN pg_namespace nco ON co.collnamespace = nco.oid) + ON a.attcollation = co.oid AND (nco.nspname <> 'pg_catalog'::name OR co.collname <> 'default'::name) WHERE NOT pg_is_other_temp_schema(nc.oid) AND a.attnum > 0 AND NOT a.attisdropped - AND (c.relkind = ANY (ARRAY['r'::"char", 'v'::"char", 'f'::"char", 'm'::"char"])) - AND (nc.nspname = ANY ($1) OR kc.r_oid IS NOT NULL) /*--filter only columns that are FK/PK or in the api schema */ - /*--AND (pg_has_role(c.relowner, 'USAGE'::text) OR has_column_privilege(c.oid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text))*/ + AND c.relkind in ('r', 'v', 'f', 'm') + -- Filter only columns that are FK/PK or in the api schema: + AND (nc.nspname = ANY ($1) OR kc.r_oid IS NOT NULL) ) SELECT table_schema, @@ -545,7 +520,6 @@ allColumns tabs = numeric_precision, column_default, udt_name - /*-- FROM information_schema.columns*/ FROM columns WHERE table_schema NOT IN ('pg_catalog', 'information_schema') ) AS info @@ -617,69 +591,36 @@ allPrimaryKeys tabs = H.Statement sql HE.noParams (decodePks tabs) True where sql = [q| - /* -- CTE to replace information_schema.table_constraints to remove owner limit - */ WITH tc AS ( - SELECT current_database()::information_schema.sql_identifier AS constraint_catalog, - nc.nspname::information_schema.sql_identifier AS constraint_schema, - c.conname::information_schema.sql_identifier AS constraint_name, - current_database()::information_schema.sql_identifier AS table_catalog, - nr.nspname::information_schema.sql_identifier AS table_schema, - r.relname::information_schema.sql_identifier AS table_name, - CASE c.contype - WHEN 'c'::"char" THEN 'CHECK'::text - WHEN 'f'::"char" THEN 'FOREIGN KEY'::text - WHEN 'p'::"char" THEN 'PRIMARY KEY'::text - WHEN 'u'::"char" THEN 'UNIQUE'::text - ELSE NULL::text - END::information_schema.character_data AS constraint_type, - CASE - WHEN c.condeferrable THEN 'YES'::text - ELSE 'NO'::text - END::information_schema.yes_or_no AS is_deferrable, - CASE - WHEN c.condeferred THEN 'YES'::text - ELSE 'NO'::text - END::information_schema.yes_or_no AS initially_deferred + SELECT + c.conname::name AS constraint_name, + nr.nspname::name AS table_schema, + r.relname::name AS table_name FROM pg_namespace nc, pg_namespace nr, pg_constraint c, pg_class r - WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace AND c.conrelid = r.oid AND (c.contype <> ALL (ARRAY['t'::"char", 'x'::"char"])) AND r.relkind = 'r'::"char" AND NOT pg_is_other_temp_schema(nr.oid) - /*--AND (pg_has_role(r.relowner, 'USAGE'::text) OR has_table_privilege(r.oid, 'INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'::text) OR has_any_column_privilege(r.oid, 'INSERT, UPDATE, REFERENCES'::text))*/ - UNION ALL - SELECT current_database()::information_schema.sql_identifier AS constraint_catalog, - nr.nspname::information_schema.sql_identifier AS constraint_schema, - (((((nr.oid::text || '_'::text) || r.oid::text) || '_'::text) || a.attnum::text) || '_not_null'::text)::information_schema.sql_identifier AS constraint_name, - current_database()::information_schema.sql_identifier AS table_catalog, - nr.nspname::information_schema.sql_identifier AS table_schema, - r.relname::information_schema.sql_identifier AS table_name, - 'CHECK'::character varying::information_schema.character_data AS constraint_type, - 'NO'::character varying::information_schema.yes_or_no AS is_deferrable, - 'NO'::character varying::information_schema.yes_or_no AS initially_deferred - FROM pg_namespace nr, - pg_class r, - pg_attribute a - WHERE nr.oid = r.relnamespace AND r.oid = a.attrelid AND a.attnotnull AND a.attnum > 0 AND NOT a.attisdropped AND r.relkind = 'r'::"char" AND NOT pg_is_other_temp_schema(nr.oid) - /*--AND (pg_has_role(r.relowner, 'USAGE'::text) OR has_table_privilege(r.oid, 'INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'::text) OR has_any_column_privilege(r.oid, 'INSERT, UPDATE, REFERENCES'::text))*/ + WHERE + nc.oid = c.connamespace + AND nr.oid = r.relnamespace + AND c.conrelid = r.oid + AND r.relkind = 'r' + AND NOT pg_is_other_temp_schema(nr.oid) + AND c.contype = 'p' ), - /* -- CTE to replace information_schema.key_column_usage to remove owner limit - */ kc AS ( - SELECT current_database()::information_schema.sql_identifier AS constraint_catalog, - ss.nc_nspname::information_schema.sql_identifier AS constraint_schema, - ss.conname::information_schema.sql_identifier AS constraint_name, - current_database()::information_schema.sql_identifier AS table_catalog, - ss.nr_nspname::information_schema.sql_identifier AS table_schema, - ss.relname::information_schema.sql_identifier AS table_name, - a.attname::information_schema.sql_identifier AS column_name, - (ss.x).n::information_schema.cardinal_number AS ordinal_position, - CASE - WHEN ss.contype = 'f'::"char" THEN information_schema._pg_index_position(ss.conindid, ss.confkey[(ss.x).n]) - ELSE NULL::integer - END::information_schema.cardinal_number AS position_in_unique_constraint + SELECT + ss.conname::name AS constraint_name, + ss.nr_nspname::name AS table_schema, + ss.relname::name AS table_name, + a.attname::name AS column_name, + (ss.x).n::integer AS ordinal_position, + CASE + WHEN ss.contype = 'f' THEN information_schema._pg_index_position(ss.conindid, ss.confkey[(ss.x).n]) + ELSE NULL::integer + END::integer AS position_in_unique_constraint FROM pg_attribute a, ( SELECT r.oid AS roid, r.relname, @@ -691,28 +632,31 @@ allPrimaryKeys tabs = c.contype, c.conindid, c.confkey, - c.confrelid, information_schema._pg_expandarray(c.conkey) AS x FROM pg_namespace nr, pg_class r, pg_namespace nc, pg_constraint c - WHERE nr.oid = r.relnamespace AND r.oid = c.conrelid AND nc.oid = c.connamespace AND (c.contype = ANY (ARRAY['p'::"char", 'u'::"char", 'f'::"char"])) AND r.relkind = 'r'::"char" AND NOT pg_is_other_temp_schema(nr.oid)) ss - WHERE ss.roid = a.attrelid AND a.attnum = (ss.x).x AND NOT a.attisdropped - /*--AND (pg_has_role(ss.relowner, 'USAGE'::text) OR has_column_privilege(ss.roid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text))*/ + WHERE + nr.oid = r.relnamespace + AND r.oid = c.conrelid + AND nc.oid = c.connamespace + AND c.contype in ('p', 'u', 'f') + AND r.relkind = 'r' + AND NOT pg_is_other_temp_schema(nr.oid) + ) ss + WHERE + ss.roid = a.attrelid + AND a.attnum = (ss.x).x + AND NOT a.attisdropped ) SELECT kc.table_schema, kc.table_name, kc.column_name FROM - /* - --information_schema.table_constraints tc, - --information_schema.key_column_usage kc - */ tc, kc WHERE - tc.constraint_type = 'PRIMARY KEY' AND kc.table_name = tc.table_name AND kc.table_schema = tc.table_schema AND kc.constraint_name = tc.constraint_name AND @@ -743,7 +687,7 @@ allSourceColumns cols pgVer = from pg_class c join pg_namespace n on n.oid = c.relnamespace join pg_rewrite r on r.ev_class = c.oid - where (c.relkind in ('v', 'm')) and n.nspname = ANY ($1) + where c.relkind in ('v', 'm') and n.nspname = ANY ($1) ), removed_subselects as( select