From 5179a888eb11719e57926cbf253399ec550d12ac Mon Sep 17 00:00:00 2001 From: malyutinvn Date: Tue, 31 Dec 2024 07:26:20 +0300 Subject: [PATCH 01/20] mock and fake functions --- sql/pgtap--1.3.1--1.3.2.sql | 418 +++++++++++++++++++++++++++++ sql/pgtap--unpackaged--0.91.0.sql | 8 + sql/pgtap.sql.in | 422 ++++++++++++++++++++++++++++++ 3 files changed, 848 insertions(+) diff --git a/sql/pgtap--1.3.1--1.3.2.sql b/sql/pgtap--1.3.1--1.3.2.sql index d0dbd80cb..b85bc5cb3 100644 --- a/sql/pgtap--1.3.1--1.3.2.sql +++ b/sql/pgtap--1.3.1--1.3.2.sql @@ -32,3 +32,421 @@ BEGIN EXCEPTION WHEN OTHERS THEN RETURN NULL; END; $$ LANGUAGE PLPGSQL STABLE; + +--added a three columns: "args", "returns", "langname" used in mock_func function +CREATE OR REPLACE VIEW tap_funky +AS +SELECT p.oid, + n.nspname AS schema, + p.proname AS name, + pg_get_userbyid(p.proowner) AS owner, + arg._types as args, + proc_return."returns", + p.prolang AS langoid, + p.proisstrict AS is_strict, + tap._prokind(p.oid) AS kind, + p.prosecdef AS is_definer, + p.proretset AS returns_set, + p.provolatile::character(1) AS volatility, + pg_function_is_visible(p.oid) AS is_visible, + l.lanname AS langname +FROM + pg_proc p +JOIN + pg_namespace n +ON + p.pronamespace = n.oid +LEFT JOIN + pg_language l +ON + l.oid = p.prolang +left join lateral ( + select string_agg(nullif(_type, '')::regtype::text, ', ') as _types + from unnest(regexp_split_to_array(p.proargtypes::text, ' ')) as _type +) as arg on true + LEFT JOIN LATERAL ( + SELECT (n.nspname::text || '.'::text) || p.proname::text AS qualified +) proc_name ON true +left join lateral ( + select + case + when n.nspname != 'pg_catalog' + then pg_get_function_result((concat(proc_name.qualified, '(', arg._types, ')')::regprocedure)::oid) + else null + end AS "returns" +) as proc_return on true; + +--this procedure creates a mock in place of a real function +create or replace procedure mock_func( + in _func_schema text + , in _func_name text + , in _func_args text + , in _return_value anyelement +) +--creates mock in place of a real function + LANGUAGE plpgsql +AS $procedure$ +declare + _mock_ddl text; + _func_result_type text; + _func_qualified_name text; + _func_language text; +begin + select + "returns" + , langname + into + _func_result_type + , _func_language + from + tap_funky + where + "schema" = _func_schema + and "name" = _func_name; + + _mock_ddl = ' + create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' + RETURNS ' || _func_result_type || ' + LANGUAGE ' || _func_language || ' + AS $function$ + select ' || quote_nullable(_return_value) || '::' || pg_typeof(_return_value) || '; + $function$;'; + execute _mock_ddl; +end $procedure$; + +CREATE OR REPLACE PROCEDURE fake_table( + IN _table_schema text[], + IN _table_name text[], + in _make_table_empty boolean default false, + IN _drop_not_null boolean DEFAULT false, + IN _drop_collation boolean DEFAULT false +) +--It frees a table from any constraint (we call such a table as a fake) +--faked table is a full copy of _table_name, but has no any constraint +--without foreign and primary things you can do whatever you want in testing context + LANGUAGE plpgsql +AS $procedure$ +declare + _table record; + _fk_table record; + _fake_ddl text; + _not_null_ddl text; +begin + for _table in + select + quote_ident(table_schema) table_schema, + quote_ident(table_name) table_name, + table_schema table_schema_l, + table_name table_name_l + from + unnest(_table_schema, _table_name) as t(table_schema, table_name) + loop + for _fk_table in + -- collect all table's relations including primary key and unique constraint + select distinct * + from ( + select + fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord + from + pg_all_foreign_keys + where + fk_schema_name = _table.table_schema_l and fk_table_name = _table.table_name_l + union all + select + fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord + from + pg_all_foreign_keys + where + pk_schema_name = _table.table_schema_l and pk_table_name = _table.table_name_l + union all + select + table_schema, table_name, constraint_name, 2 as ord + from + information_schema.table_constraints + where + table_schema = _table.table_schema_l + and table_name = _table.table_name_l + and constraint_type in ('PRIMARY KEY', 'UNIQUE') + ) as t + order by ord + loop + _fake_ddl = 'alter table ' || _fk_table.table_schema || '.' || _fk_table.table_name || ' + drop constraint ' || _fk_table.constraint_name || ';'; + execute _fake_ddl; + end loop; + + if _make_table_empty then + _fake_ddl = 'truncate table ' || _table.table_schema || '.' || _table.table_name || ';'; + execute _fake_ddl; + end if; + + --Free table from not null constraints + _fake_ddl = 'alter table ' || _table.table_schema || '.' || _table.table_name || ' '; + if _drop_not_null then + select + string_agg('alter column ' || t.attname || ' drop not null', ', ') + into + _not_null_ddl + from + pg_catalog.pg_attribute t + where t.attrelid = (_table.table_schema || '.' || _table.table_name)::regclass + and t.attnum > 0 and attnotnull; + + _fake_ddl = _fake_ddl || _not_null_ddl || ';'; + else + _fake_ddl = null; + end if; + + if _fake_ddl is not null then + execute _fake_ddl; + end if; + end loop; +end $procedure$; + +CREATE OR REPLACE FUNCTION _runner(text[], text[], text[], text[], text[]) + RETURNS SETOF text + LANGUAGE plpgsql +AS $function$ +DECLARE + startup ALIAS FOR $1; + shutdown ALIAS FOR $2; + setup ALIAS FOR $3; + teardown ALIAS FOR $4; + tests ALIAS FOR $5; + tap TEXT; + tfaild INTEGER := 0; + ffaild INTEGER := 0; + tnumb INTEGER := 0; + fnumb INTEGER := 0; + tok BOOLEAN := TRUE; +BEGIN + BEGIN + -- No plan support. + PERFORM * FROM no_plan(); + FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; + EXCEPTION + -- Catch all exceptions and simply rethrow custom exceptions. This + -- will roll back everything in the above block. + WHEN raise_exception THEN RAISE EXCEPTION '%', SQLERRM; + END; + + -- Record how startup tests have failed. + tfaild := num_failed(); + + FOR i IN 1..COALESCE(array_upper(tests, 1), 0) LOOP + + -- What subtest are we running? + RETURN NEXT diag_test_name('Subtest: ' || tests[i]); + + -- Reset the results. + tok := TRUE; + tnumb := COALESCE(_get('curr_test'), 0); + + IF tnumb > 0 THEN + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; + PERFORM _set('curr_test', 0); + PERFORM _set('failed', 0); + END IF; + + DECLARE + errstate text; + errmsg text; + detail text; + hint text; + context text; + schname text; + tabname text; + colname text; + chkname text; + typname text; + BEGIN + BEGIN + -- Run the setup functions. + FOR tap IN SELECT * FROM _runem(setup, false) LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Create a temp table to collect call count + execute 'create temp table call_count( + routine_schema text not null + , routine_name text not null + , call_cnt int + ) ON COMMIT DROP;'; + + -- Run the actual test function. + FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Run the teardown functions. + FOR tap IN SELECT * FROM _runem(teardown, false) LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Emit the plan. + fnumb := COALESCE(_get('curr_test'), 0); + RETURN NEXT ' 1..' || fnumb; + + -- Emit any error messages. + IF fnumb = 0 THEN + RETURN NEXT ' # No tests run!'; + tok = false; + ELSE + -- Report failures. + ffaild := num_failed(); + IF ffaild > 0 THEN + tok := FALSE; + RETURN NEXT ' ' || diag( + 'Looks like you failed ' || ffaild || ' test' || + CASE ffaild WHEN 1 THEN '' ELSE 's' END + || ' of ' || fnumb + ); + END IF; + END IF; + + EXCEPTION WHEN OTHERS THEN + -- Something went wrong. Record that fact. + errstate := SQLSTATE; + errmsg := SQLERRM; + GET STACKED DIAGNOSTICS + detail = PG_EXCEPTION_DETAIL, + hint = PG_EXCEPTION_HINT, + context = PG_EXCEPTION_CONTEXT, + schname = SCHEMA_NAME, + tabname = TABLE_NAME, + colname = COLUMN_NAME, + chkname = CONSTRAINT_NAME, + typname = PG_DATATYPE_NAME; + END; + + -- Always raise an exception to rollback any changes. + RAISE EXCEPTION '__TAP_ROLLBACK__'; + + EXCEPTION WHEN raise_exception THEN + IF errmsg IS NOT NULL THEN + -- Something went wrong. Emit the error message. + tok := FALSE; + RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( + errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname + )), '^', ' ', 'gn'); + errmsg := NULL; + END IF; + END; + + -- Restore the sequence. + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; + PERFORM _set('curr_test', tnumb); + PERFORM _set('failed', tfaild); + + -- Record this test. + RETURN NEXT ok(tok, tests[i]); + IF NOT tok THEN tfaild := tfaild + 1; END IF; + + END LOOP; + + -- Run the shutdown functions. + FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; + + -- Finish up. + FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP + RETURN NEXT tap; + END LOOP; + + -- Clean up and return. + PERFORM _cleanup(); + RETURN; +END; +$function$; + +create or replace procedure willing_count_calls_of(_proc_schema text, _proc_name text, _proc_args text) + LANGUAGE plpgsql +AS $procedure$ +declare + _ddl text; +begin + insert into call_count(routine_schema, routine_name, call_cnt) values(_proc_schema, _proc_name, 0); + _ddl = ' + create or replace procedure ' || quote_ident(_proc_schema) || '.' || quote_ident(_proc_name) || _proc_args || ' + LANGUAGE plpgsql + AS $proc$ + begin + update call_count set call_cnt = call_cnt + 1 + where routine_schema = ' || quote_literal(_proc_schema) || ' + and routine_name = ' || quote_literal(_proc_name) || '; + end + $proc$;'; + raise notice '%', _ddl; + execute _ddl; +end +$procedure$; + +create or replace function called_once(_proc_schema text, _proc_name text) +returns setof text as $$ +begin + return query select called_times(1, _proc_schema, _proc_name); +end $$ +LANGUAGE plpgsql; + +create or replace function called_times(_call_count int, _proc_schema text, _proc_name text) +returns setof text as $$ +declare + _actual_call_count int; +begin + select + call_cnt + into + _actual_call_count + from + call_count + where + routine_schema = _proc_schema + and routine_name = _proc_name; + + return query select ok( + _actual_call_count = _call_count + , format('routine %L.%L must have been called %L times', _proc_schema, _proc_name, _call_count) + ); +end $$ +LANGUAGE plpgsql; + +create or replace function drop_prepared_statement(_statement_name text) +returns bool as $$ +begin + if exists(select * from pg_prepared_statements where "name" = _statement_name) then + EXECUTE format('deallocate %I;', _statement_name); + return true; + end if; + return false; +end +$$ +language plpgsql; + +create or replace procedure print_table_as_json(in _table_schema text, in _table_name text) + language plpgsql +AS $procedure$ +declare + _ddl text; + _json text; + _columns text; +--returns a query which you can execute and see your table as normal dataset +--note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows +begin + _ddl = ' + select json_agg( + array(select ' || quote_ident(_table_name) || ' from ' || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || ' limit 1000 + )) as j;'; + execute _ddl into _json; + _json = '[' || ltrim(rtrim(_json::text, ']'), '[') || ']'; + + select string_agg(concat(c.column_name, ' ', case when lower(c.data_type) = 'array' then e.data_type || '[]' else c.data_type end), ', ') + into _columns + from information_schema."columns" c + left join information_schema.element_types e + on ((c.table_catalog, c.table_schema, c.table_name, 'TABLE', c.dtd_identifier) + = (e.object_catalog, e.object_schema, e.object_name, e.object_type, e.collection_type_identifier)) + where c.table_schema = _table_schema + and c.table_name = _table_name; + + _json = $$select * from /*$$ || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || $$*/ json_to_recordset('$$ || _json || $$') as t($$ || _columns || $$)$$; + raise notice '%', _json; +end $procedure$; \ No newline at end of file diff --git a/sql/pgtap--unpackaged--0.91.0.sql b/sql/pgtap--unpackaged--0.91.0.sql index e8875c32f..cec4c09a2 100644 --- a/sql/pgtap--unpackaged--0.91.0.sql +++ b/sql/pgtap--unpackaged--0.91.0.sql @@ -712,3 +712,11 @@ ALTER EXTENSION pgtap ADD FUNCTION columns_are( NAME, NAME[] ); ALTER EXTENSION pgtap ADD FUNCTION _get_db_owner( NAME ); ALTER EXTENSION pgtap ADD FUNCTION db_owner_is ( NAME, NAME, TEXT ); ALTER EXTENSION pgtap ADD FUNCTION db_owner_is ( NAME, NAME ); + +ALTER EXTENSION pgtap ADD PROCEDURE mock_func( TEXT, TEXT, TEXT, ANYELEMENT ); +ALTER EXTENSION pgtap ADD PROCEDURE fake_table( TEXT[], TEXT[], BOOL, BOOL ); +ALTER EXTENSION pgtap ADD PROCEDURE willing_count_calls_of( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION called_once( TEXT , TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION called_times( INT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION drop_prepared_statement( TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION print_table_as_json( TEXT, TEXT ); \ No newline at end of file diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 5f4cd1592..ed94e3728 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -11446,3 +11446,425 @@ RETURNS TEXT AS $$ 'Function ' || quote_ident($1) || '() should not be a procedure' ); $$ LANGUAGE sql; + +--added a three columns: "args", "returns", "langname" used in mock_func function +CREATE OR REPLACE VIEW tap_funky +AS +SELECT p.oid, + n.nspname AS schema, + p.proname AS name, + pg_get_userbyid(p.proowner) AS owner, + arg._types as args, + proc_return."returns", + p.prolang AS langoid, + p.proisstrict AS is_strict, + tap._prokind(p.oid) AS kind, + p.prosecdef AS is_definer, + p.proretset AS returns_set, + p.provolatile::character(1) AS volatility, + pg_function_is_visible(p.oid) AS is_visible, + l.lanname AS langname +FROM + pg_proc p +JOIN + pg_namespace n +ON + p.pronamespace = n.oid +LEFT JOIN + pg_language l +ON + l.oid = p.prolang +left join lateral ( + select string_agg(nullif(_type, '')::regtype::text, ', ') as _types + from unnest(regexp_split_to_array(p.proargtypes::text, ' ')) as _type +) as arg on true + LEFT JOIN LATERAL ( + SELECT (n.nspname::text || '.'::text) || p.proname::text AS qualified +) proc_name ON true +left join lateral ( + select + case + when n.nspname != 'pg_catalog' + then pg_get_function_result((concat(proc_name.qualified, '(', arg._types, ')')::regprocedure)::oid) + else null + end AS "returns" +) as proc_return on true; + +--this procedure creates a mock in place of a real function +create or replace procedure mock_func( + in _func_schema text + , in _func_name text + , in _func_args text + , in _return_value anyelement +) +--creates mock in place of a real function + LANGUAGE plpgsql +AS $procedure$ +declare + _mock_ddl text; + _func_result_type text; + _func_qualified_name text; + _func_language text; +begin + select + "returns" + , langname + into + _func_result_type + , _func_language + from + tap_funky + where + "schema" = _func_schema + and "name" = _func_name; + + _mock_ddl = ' + create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' + RETURNS ' || _func_result_type || ' + LANGUAGE ' || _func_language || ' + AS $function$ + select ' || quote_nullable(_return_value) || '::' || pg_typeof(_return_value) || '; + $function$;'; + execute _mock_ddl; +end $procedure$; + +CREATE OR REPLACE PROCEDURE fake_table( + IN _table_schema text[], + IN _table_name text[], + IN _make_table_empty boolean default false, + IN _drop_not_null boolean DEFAULT false, + IN _drop_collation boolean DEFAULT false +) +--It frees a table from any constraint (we call such a table as a fake) +--faked table is a full copy of _table_name, but has no any constraint +--without foreign and primary things you can do whatever you want in testing context + LANGUAGE plpgsql +AS $procedure$ +declare + _table record; + _fk_table record; + _fake_ddl text; + _not_null_ddl text; +begin + for _table in + select + quote_ident(table_schema) table_schema, + quote_ident(table_name) table_name, + table_schema table_schema_l, + table_name table_name_l + from + unnest(_table_schema, _table_name) as t(table_schema, table_name) + loop + for _fk_table in + -- collect all table's relations including primary key and unique constraint + select distinct * + from ( + select + fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord + from + pg_all_foreign_keys + where + fk_schema_name = _table.table_schema_l and fk_table_name = _table.table_name_l + union all + select + fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord + from + pg_all_foreign_keys + where + pk_schema_name = _table.table_schema_l and pk_table_name = _table.table_name_l + union all + select + table_schema, table_name, constraint_name, 2 as ord + from + information_schema.table_constraints + where + table_schema = _table.table_schema_l + and table_name = _table.table_name_l + and constraint_type in ('PRIMARY KEY', 'UNIQUE') + ) as t + order by ord + loop + _fake_ddl = 'alter table ' || _fk_table.table_schema || '.' || _fk_table.table_name || ' + drop constraint ' || _fk_table.constraint_name || ';'; + execute _fake_ddl; + end loop; + + --make table empty + if _make_table_empty then + _fake_ddl = 'truncate table ' || _table.table_schema || '.' || _table.table_name || ';'; + execute _fake_ddl; + end if; + + --Free table from not null constraints + _fake_ddl = 'alter table ' || _table.table_schema || '.' || _table.table_name || ' '; + if _drop_not_null then + select + string_agg('alter column ' || t.attname || ' drop not null', ', ') + into + _not_null_ddl + from + pg_catalog.pg_attribute t + where t.attrelid = (_table.table_schema || '.' || _table.table_name)::regclass + and t.attnum > 0 and attnotnull; + + _fake_ddl = _fake_ddl || _not_null_ddl || ';'; + else + _fake_ddl = null; + end if; + + if _fake_ddl is not null then + execute _fake_ddl; + end if; + end loop; +end $procedure$; + +CREATE OR REPLACE FUNCTION _runner(text[], text[], text[], text[], text[]) + RETURNS SETOF text + LANGUAGE plpgsql +AS $function$ +DECLARE + startup ALIAS FOR $1; + shutdown ALIAS FOR $2; + setup ALIAS FOR $3; + teardown ALIAS FOR $4; + tests ALIAS FOR $5; + tap TEXT; + tfaild INTEGER := 0; + ffaild INTEGER := 0; + tnumb INTEGER := 0; + fnumb INTEGER := 0; + tok BOOLEAN := TRUE; +BEGIN + BEGIN + -- No plan support. + PERFORM * FROM no_plan(); + FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; + EXCEPTION + -- Catch all exceptions and simply rethrow custom exceptions. This + -- will roll back everything in the above block. + WHEN raise_exception THEN RAISE EXCEPTION '%', SQLERRM; + END; + + -- Record how startup tests have failed. + tfaild := num_failed(); + + FOR i IN 1..COALESCE(array_upper(tests, 1), 0) LOOP + + -- What subtest are we running? + RETURN NEXT diag_test_name('Subtest: ' || tests[i]); + + -- Reset the results. + tok := TRUE; + tnumb := COALESCE(_get('curr_test'), 0); + + IF tnumb > 0 THEN + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; + PERFORM _set('curr_test', 0); + PERFORM _set('failed', 0); + END IF; + + DECLARE + errstate text; + errmsg text; + detail text; + hint text; + context text; + schname text; + tabname text; + colname text; + chkname text; + typname text; + BEGIN + BEGIN + -- Run the setup functions. + FOR tap IN SELECT * FROM _runem(setup, false) LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Create a temp table to collect call count + execute 'create temp table call_count( + routine_schema text not null + , routine_name text not null + , call_cnt int + ) ON COMMIT DROP;'; + + -- Run the actual test function. + FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Run the teardown functions. + FOR tap IN SELECT * FROM _runem(teardown, false) LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Emit the plan. + fnumb := COALESCE(_get('curr_test'), 0); + RETURN NEXT ' 1..' || fnumb; + + -- Emit any error messages. + IF fnumb = 0 THEN + RETURN NEXT ' # No tests run!'; + tok = false; + ELSE + -- Report failures. + ffaild := num_failed(); + IF ffaild > 0 THEN + tok := FALSE; + RETURN NEXT ' ' || diag( + 'Looks like you failed ' || ffaild || ' test' || + CASE ffaild WHEN 1 THEN '' ELSE 's' END + || ' of ' || fnumb + ); + END IF; + END IF; + + EXCEPTION WHEN OTHERS THEN + -- Something went wrong. Record that fact. + errstate := SQLSTATE; + errmsg := SQLERRM; + GET STACKED DIAGNOSTICS + detail = PG_EXCEPTION_DETAIL, + hint = PG_EXCEPTION_HINT, + context = PG_EXCEPTION_CONTEXT, + schname = SCHEMA_NAME, + tabname = TABLE_NAME, + colname = COLUMN_NAME, + chkname = CONSTRAINT_NAME, + typname = PG_DATATYPE_NAME; + END; + + -- Always raise an exception to rollback any changes. + RAISE EXCEPTION '__TAP_ROLLBACK__'; + + EXCEPTION WHEN raise_exception THEN + IF errmsg IS NOT NULL THEN + -- Something went wrong. Emit the error message. + tok := FALSE; + RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( + errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname + )), '^', ' ', 'gn'); + errmsg := NULL; + END IF; + END; + + -- Restore the sequence. + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; + PERFORM _set('curr_test', tnumb); + PERFORM _set('failed', tfaild); + + -- Record this test. + RETURN NEXT ok(tok, tests[i]); + IF NOT tok THEN tfaild := tfaild + 1; END IF; + + END LOOP; + + -- Run the shutdown functions. + FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; + + -- Finish up. + FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP + RETURN NEXT tap; + END LOOP; + + -- Clean up and return. + PERFORM _cleanup(); + RETURN; +END; +$function$; + +create or replace procedure willing_count_calls_of(_proc_schema text, _proc_name text, _proc_args text) +--it counts of calls of a routine during execution + LANGUAGE plpgsql +AS $procedure$ +declare + _ddl text; +begin + insert into call_count(routine_schema, routine_name, call_cnt) values(_proc_schema, _proc_name, 0); + _ddl = ' + create or replace procedure ' || quote_ident(_proc_schema) || '.' || quote_ident(_proc_name) || _proc_args || ' + LANGUAGE plpgsql + AS $proc$ + begin + update call_count set call_cnt = call_cnt + 1 + where routine_schema = ' || quote_literal(_proc_schema) || ' + and routine_name = ' || quote_literal(_proc_name) || '; + end + $proc$;'; + execute _ddl; +end +$procedure$; + +create or replace function called_once(_proc_schema text, _proc_name text) +--Insures that a routine have been called only once +returns setof text as $$ +begin + return query select called_times(1, _proc_schema, _proc_name); +end $$ +LANGUAGE plpgsql; + +create or replace function called_times(_call_count int, _proc_schema text, _proc_name text) +--Insures that a routine have been called specified times +returns setof text as $$ +declare + _actual_call_count int; +begin + select + call_cnt + into + _actual_call_count + from + call_count + where + routine_schema = _proc_schema + and routine_name = _proc_name; + + return query select ok( + _actual_call_count = _call_count + , format('routine %L.%L must have been called %L times', _proc_schema, _proc_name, _call_count) + ); +end $$ +LANGUAGE plpgsql; + +create or replace function drop_prepared_statement(_statement_name text) +--It drops a prepared statement to be sure you can run a test many times +returns bool as $$ +begin + if exists(select * from pg_prepared_statements where "name" = _statement_name) then + EXECUTE format('deallocate %I;', _statement_name); + return true; + end if; + return false; +end +$$ +language plpgsql; + +create or replace procedure print_table_as_json(in _table_schema text, in _table_name text) + language plpgsql +AS $procedure$ +declare + _ddl text; + _json text; + _columns text; +--returns a query which you can execute and see your table as normal dataset +--note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows +begin + _ddl = ' + select json_agg( + array(select ' || quote_ident(_table_name) || ' from ' || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || ' limit 1000 + )) as j;'; + execute _ddl into _json; + _json = '[' || ltrim(rtrim(_json::text, ']'), '[') || ']'; + + select string_agg(concat(c.column_name, ' ', case when lower(c.data_type) = 'array' then e.data_type || '[]' else c.data_type end), ', ') + into _columns + from information_schema."columns" c + left join information_schema.element_types e + on ((c.table_catalog, c.table_schema, c.table_name, 'TABLE', c.dtd_identifier) + = (e.object_catalog, e.object_schema, e.object_name, e.object_type, e.collection_type_identifier)) + where c.table_schema = _table_schema + and c.table_name = _table_name; + + _json = $$select * from /*$$ || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || $$*/ json_to_recordset('$$ || _json || $$') as t($$ || _columns || $$)$$; + raise notice '%', _json; +end $procedure$; \ No newline at end of file From 463c367304ef0aa75e227dda32db366ba7d7c543 Mon Sep 17 00:00:00 2001 From: v-maliutin Date: Tue, 31 Dec 2024 09:02:52 +0300 Subject: [PATCH 02/20] Add files via upload mock and fake functions --- pgtap--1.3.1--1.3.2.sql | 452 ++ pgtap--unpackaged--0.91.0.sql | 722 ++ pgtap.sql.in | 11870 ++++++++++++++++++++++++++++++++ 3 files changed, 13044 insertions(+) create mode 100644 pgtap--1.3.1--1.3.2.sql create mode 100644 pgtap--unpackaged--0.91.0.sql create mode 100644 pgtap.sql.in diff --git a/pgtap--1.3.1--1.3.2.sql b/pgtap--1.3.1--1.3.2.sql new file mode 100644 index 000000000..b85bc5cb3 --- /dev/null +++ b/pgtap--1.3.1--1.3.2.sql @@ -0,0 +1,452 @@ +DROP FUNCTION parse_type(type text, OUT typid oid, OUT typmod int4); + +CREATE OR REPLACE FUNCTION format_type_string ( TEXT ) +RETURNS TEXT AS $$ +DECLARE + want_type TEXT := $1; + typmodin_arg cstring[]; + typmodin_func regproc; + typmod int; +BEGIN + IF want_type::regtype = 'interval'::regtype THEN + -- RAISE NOTICE 'cannot resolve: %', want_type; -- TODO + RETURN want_type; + END IF; + + -- Extract type modifier from type declaration and format as cstring[] literal. + typmodin_arg := translate(substring(want_type FROM '[(][^")]+[)]'), '()', '{}'); + + -- Find typmodin function for want_type. + SELECT typmodin INTO typmodin_func + FROM pg_catalog.pg_type + WHERE oid = want_type::regtype; + + IF typmodin_func = 0 THEN + -- Easy: types without typemods. + RETURN format_type(want_type::regtype, null); + END IF; + + -- Get typemod via type-specific typmodin function. + EXECUTE format('SELECT %I(%L)', typmodin_func, typmodin_arg) INTO typmod; + RETURN format_type(want_type::regtype, typmod); +EXCEPTION WHEN OTHERS THEN RETURN NULL; +END; +$$ LANGUAGE PLPGSQL STABLE; + +--added a three columns: "args", "returns", "langname" used in mock_func function +CREATE OR REPLACE VIEW tap_funky +AS +SELECT p.oid, + n.nspname AS schema, + p.proname AS name, + pg_get_userbyid(p.proowner) AS owner, + arg._types as args, + proc_return."returns", + p.prolang AS langoid, + p.proisstrict AS is_strict, + tap._prokind(p.oid) AS kind, + p.prosecdef AS is_definer, + p.proretset AS returns_set, + p.provolatile::character(1) AS volatility, + pg_function_is_visible(p.oid) AS is_visible, + l.lanname AS langname +FROM + pg_proc p +JOIN + pg_namespace n +ON + p.pronamespace = n.oid +LEFT JOIN + pg_language l +ON + l.oid = p.prolang +left join lateral ( + select string_agg(nullif(_type, '')::regtype::text, ', ') as _types + from unnest(regexp_split_to_array(p.proargtypes::text, ' ')) as _type +) as arg on true + LEFT JOIN LATERAL ( + SELECT (n.nspname::text || '.'::text) || p.proname::text AS qualified +) proc_name ON true +left join lateral ( + select + case + when n.nspname != 'pg_catalog' + then pg_get_function_result((concat(proc_name.qualified, '(', arg._types, ')')::regprocedure)::oid) + else null + end AS "returns" +) as proc_return on true; + +--this procedure creates a mock in place of a real function +create or replace procedure mock_func( + in _func_schema text + , in _func_name text + , in _func_args text + , in _return_value anyelement +) +--creates mock in place of a real function + LANGUAGE plpgsql +AS $procedure$ +declare + _mock_ddl text; + _func_result_type text; + _func_qualified_name text; + _func_language text; +begin + select + "returns" + , langname + into + _func_result_type + , _func_language + from + tap_funky + where + "schema" = _func_schema + and "name" = _func_name; + + _mock_ddl = ' + create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' + RETURNS ' || _func_result_type || ' + LANGUAGE ' || _func_language || ' + AS $function$ + select ' || quote_nullable(_return_value) || '::' || pg_typeof(_return_value) || '; + $function$;'; + execute _mock_ddl; +end $procedure$; + +CREATE OR REPLACE PROCEDURE fake_table( + IN _table_schema text[], + IN _table_name text[], + in _make_table_empty boolean default false, + IN _drop_not_null boolean DEFAULT false, + IN _drop_collation boolean DEFAULT false +) +--It frees a table from any constraint (we call such a table as a fake) +--faked table is a full copy of _table_name, but has no any constraint +--without foreign and primary things you can do whatever you want in testing context + LANGUAGE plpgsql +AS $procedure$ +declare + _table record; + _fk_table record; + _fake_ddl text; + _not_null_ddl text; +begin + for _table in + select + quote_ident(table_schema) table_schema, + quote_ident(table_name) table_name, + table_schema table_schema_l, + table_name table_name_l + from + unnest(_table_schema, _table_name) as t(table_schema, table_name) + loop + for _fk_table in + -- collect all table's relations including primary key and unique constraint + select distinct * + from ( + select + fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord + from + pg_all_foreign_keys + where + fk_schema_name = _table.table_schema_l and fk_table_name = _table.table_name_l + union all + select + fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord + from + pg_all_foreign_keys + where + pk_schema_name = _table.table_schema_l and pk_table_name = _table.table_name_l + union all + select + table_schema, table_name, constraint_name, 2 as ord + from + information_schema.table_constraints + where + table_schema = _table.table_schema_l + and table_name = _table.table_name_l + and constraint_type in ('PRIMARY KEY', 'UNIQUE') + ) as t + order by ord + loop + _fake_ddl = 'alter table ' || _fk_table.table_schema || '.' || _fk_table.table_name || ' + drop constraint ' || _fk_table.constraint_name || ';'; + execute _fake_ddl; + end loop; + + if _make_table_empty then + _fake_ddl = 'truncate table ' || _table.table_schema || '.' || _table.table_name || ';'; + execute _fake_ddl; + end if; + + --Free table from not null constraints + _fake_ddl = 'alter table ' || _table.table_schema || '.' || _table.table_name || ' '; + if _drop_not_null then + select + string_agg('alter column ' || t.attname || ' drop not null', ', ') + into + _not_null_ddl + from + pg_catalog.pg_attribute t + where t.attrelid = (_table.table_schema || '.' || _table.table_name)::regclass + and t.attnum > 0 and attnotnull; + + _fake_ddl = _fake_ddl || _not_null_ddl || ';'; + else + _fake_ddl = null; + end if; + + if _fake_ddl is not null then + execute _fake_ddl; + end if; + end loop; +end $procedure$; + +CREATE OR REPLACE FUNCTION _runner(text[], text[], text[], text[], text[]) + RETURNS SETOF text + LANGUAGE plpgsql +AS $function$ +DECLARE + startup ALIAS FOR $1; + shutdown ALIAS FOR $2; + setup ALIAS FOR $3; + teardown ALIAS FOR $4; + tests ALIAS FOR $5; + tap TEXT; + tfaild INTEGER := 0; + ffaild INTEGER := 0; + tnumb INTEGER := 0; + fnumb INTEGER := 0; + tok BOOLEAN := TRUE; +BEGIN + BEGIN + -- No plan support. + PERFORM * FROM no_plan(); + FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; + EXCEPTION + -- Catch all exceptions and simply rethrow custom exceptions. This + -- will roll back everything in the above block. + WHEN raise_exception THEN RAISE EXCEPTION '%', SQLERRM; + END; + + -- Record how startup tests have failed. + tfaild := num_failed(); + + FOR i IN 1..COALESCE(array_upper(tests, 1), 0) LOOP + + -- What subtest are we running? + RETURN NEXT diag_test_name('Subtest: ' || tests[i]); + + -- Reset the results. + tok := TRUE; + tnumb := COALESCE(_get('curr_test'), 0); + + IF tnumb > 0 THEN + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; + PERFORM _set('curr_test', 0); + PERFORM _set('failed', 0); + END IF; + + DECLARE + errstate text; + errmsg text; + detail text; + hint text; + context text; + schname text; + tabname text; + colname text; + chkname text; + typname text; + BEGIN + BEGIN + -- Run the setup functions. + FOR tap IN SELECT * FROM _runem(setup, false) LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Create a temp table to collect call count + execute 'create temp table call_count( + routine_schema text not null + , routine_name text not null + , call_cnt int + ) ON COMMIT DROP;'; + + -- Run the actual test function. + FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Run the teardown functions. + FOR tap IN SELECT * FROM _runem(teardown, false) LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Emit the plan. + fnumb := COALESCE(_get('curr_test'), 0); + RETURN NEXT ' 1..' || fnumb; + + -- Emit any error messages. + IF fnumb = 0 THEN + RETURN NEXT ' # No tests run!'; + tok = false; + ELSE + -- Report failures. + ffaild := num_failed(); + IF ffaild > 0 THEN + tok := FALSE; + RETURN NEXT ' ' || diag( + 'Looks like you failed ' || ffaild || ' test' || + CASE ffaild WHEN 1 THEN '' ELSE 's' END + || ' of ' || fnumb + ); + END IF; + END IF; + + EXCEPTION WHEN OTHERS THEN + -- Something went wrong. Record that fact. + errstate := SQLSTATE; + errmsg := SQLERRM; + GET STACKED DIAGNOSTICS + detail = PG_EXCEPTION_DETAIL, + hint = PG_EXCEPTION_HINT, + context = PG_EXCEPTION_CONTEXT, + schname = SCHEMA_NAME, + tabname = TABLE_NAME, + colname = COLUMN_NAME, + chkname = CONSTRAINT_NAME, + typname = PG_DATATYPE_NAME; + END; + + -- Always raise an exception to rollback any changes. + RAISE EXCEPTION '__TAP_ROLLBACK__'; + + EXCEPTION WHEN raise_exception THEN + IF errmsg IS NOT NULL THEN + -- Something went wrong. Emit the error message. + tok := FALSE; + RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( + errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname + )), '^', ' ', 'gn'); + errmsg := NULL; + END IF; + END; + + -- Restore the sequence. + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; + PERFORM _set('curr_test', tnumb); + PERFORM _set('failed', tfaild); + + -- Record this test. + RETURN NEXT ok(tok, tests[i]); + IF NOT tok THEN tfaild := tfaild + 1; END IF; + + END LOOP; + + -- Run the shutdown functions. + FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; + + -- Finish up. + FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP + RETURN NEXT tap; + END LOOP; + + -- Clean up and return. + PERFORM _cleanup(); + RETURN; +END; +$function$; + +create or replace procedure willing_count_calls_of(_proc_schema text, _proc_name text, _proc_args text) + LANGUAGE plpgsql +AS $procedure$ +declare + _ddl text; +begin + insert into call_count(routine_schema, routine_name, call_cnt) values(_proc_schema, _proc_name, 0); + _ddl = ' + create or replace procedure ' || quote_ident(_proc_schema) || '.' || quote_ident(_proc_name) || _proc_args || ' + LANGUAGE plpgsql + AS $proc$ + begin + update call_count set call_cnt = call_cnt + 1 + where routine_schema = ' || quote_literal(_proc_schema) || ' + and routine_name = ' || quote_literal(_proc_name) || '; + end + $proc$;'; + raise notice '%', _ddl; + execute _ddl; +end +$procedure$; + +create or replace function called_once(_proc_schema text, _proc_name text) +returns setof text as $$ +begin + return query select called_times(1, _proc_schema, _proc_name); +end $$ +LANGUAGE plpgsql; + +create or replace function called_times(_call_count int, _proc_schema text, _proc_name text) +returns setof text as $$ +declare + _actual_call_count int; +begin + select + call_cnt + into + _actual_call_count + from + call_count + where + routine_schema = _proc_schema + and routine_name = _proc_name; + + return query select ok( + _actual_call_count = _call_count + , format('routine %L.%L must have been called %L times', _proc_schema, _proc_name, _call_count) + ); +end $$ +LANGUAGE plpgsql; + +create or replace function drop_prepared_statement(_statement_name text) +returns bool as $$ +begin + if exists(select * from pg_prepared_statements where "name" = _statement_name) then + EXECUTE format('deallocate %I;', _statement_name); + return true; + end if; + return false; +end +$$ +language plpgsql; + +create or replace procedure print_table_as_json(in _table_schema text, in _table_name text) + language plpgsql +AS $procedure$ +declare + _ddl text; + _json text; + _columns text; +--returns a query which you can execute and see your table as normal dataset +--note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows +begin + _ddl = ' + select json_agg( + array(select ' || quote_ident(_table_name) || ' from ' || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || ' limit 1000 + )) as j;'; + execute _ddl into _json; + _json = '[' || ltrim(rtrim(_json::text, ']'), '[') || ']'; + + select string_agg(concat(c.column_name, ' ', case when lower(c.data_type) = 'array' then e.data_type || '[]' else c.data_type end), ', ') + into _columns + from information_schema."columns" c + left join information_schema.element_types e + on ((c.table_catalog, c.table_schema, c.table_name, 'TABLE', c.dtd_identifier) + = (e.object_catalog, e.object_schema, e.object_name, e.object_type, e.collection_type_identifier)) + where c.table_schema = _table_schema + and c.table_name = _table_name; + + _json = $$select * from /*$$ || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || $$*/ json_to_recordset('$$ || _json || $$') as t($$ || _columns || $$)$$; + raise notice '%', _json; +end $procedure$; \ No newline at end of file diff --git a/pgtap--unpackaged--0.91.0.sql b/pgtap--unpackaged--0.91.0.sql new file mode 100644 index 000000000..cec4c09a2 --- /dev/null +++ b/pgtap--unpackaged--0.91.0.sql @@ -0,0 +1,722 @@ +ALTER EXTENSION pgtap ADD FUNCTION pg_version(); +ALTER EXTENSION pgtap ADD FUNCTION pg_version_num(); +ALTER EXTENSION pgtap ADD FUNCTION os_name(); +ALTER EXTENSION pgtap ADD FUNCTION pgtap_version(); +ALTER EXTENSION pgtap ADD FUNCTION plan( integer ); +ALTER EXTENSION pgtap ADD FUNCTION no_plan(); +ALTER EXTENSION pgtap ADD FUNCTION _get ( text ); +ALTER EXTENSION pgtap ADD FUNCTION _get_latest ( text ); +ALTER EXTENSION pgtap ADD FUNCTION _get_latest ( text, integer ); +ALTER EXTENSION pgtap ADD FUNCTION _get_note ( text ); +ALTER EXTENSION pgtap ADD FUNCTION _get_note ( integer ); +ALTER EXTENSION pgtap ADD FUNCTION _set ( text, integer, text ); +ALTER EXTENSION pgtap ADD FUNCTION _set ( text, integer ); +ALTER EXTENSION pgtap ADD FUNCTION _set ( integer, integer ); +ALTER EXTENSION pgtap ADD FUNCTION _add ( text, integer, text ); +ALTER EXTENSION pgtap ADD FUNCTION _add ( text, integer ); +ALTER EXTENSION pgtap ADD FUNCTION add_result ( bool, bool, text, text, text ); +ALTER EXTENSION pgtap ADD FUNCTION num_failed (); +ALTER EXTENSION pgtap ADD FUNCTION _finish ( INTEGER, INTEGER, INTEGER); +ALTER EXTENSION pgtap ADD FUNCTION finish (); +ALTER EXTENSION pgtap ADD FUNCTION diag ( msg text ); +ALTER EXTENSION pgtap ADD FUNCTION diag ( msg anyelement ); +ALTER EXTENSION pgtap ADD FUNCTION diag( VARIADIC text[] ); +ALTER EXTENSION pgtap ADD FUNCTION diag( VARIADIC anyarray ); +ALTER EXTENSION pgtap ADD FUNCTION ok ( boolean, text ); +ALTER EXTENSION pgtap ADD FUNCTION ok ( boolean ); +ALTER EXTENSION pgtap ADD FUNCTION is (anyelement, anyelement, text); +ALTER EXTENSION pgtap ADD FUNCTION is (anyelement, anyelement); +ALTER EXTENSION pgtap ADD FUNCTION isnt (anyelement, anyelement, text); +ALTER EXTENSION pgtap ADD FUNCTION isnt (anyelement, anyelement); +ALTER EXTENSION pgtap ADD FUNCTION _alike ( BOOLEAN, ANYELEMENT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION matches ( anyelement, text, text ); +ALTER EXTENSION pgtap ADD FUNCTION matches ( anyelement, text ); +ALTER EXTENSION pgtap ADD FUNCTION imatches ( anyelement, text, text ); +ALTER EXTENSION pgtap ADD FUNCTION imatches ( anyelement, text ); +ALTER EXTENSION pgtap ADD FUNCTION alike ( anyelement, text, text ); +ALTER EXTENSION pgtap ADD FUNCTION alike ( anyelement, text ); +ALTER EXTENSION pgtap ADD FUNCTION ialike ( anyelement, text, text ); +ALTER EXTENSION pgtap ADD FUNCTION ialike ( anyelement, text ); +ALTER EXTENSION pgtap ADD FUNCTION _unalike ( BOOLEAN, ANYELEMENT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION doesnt_match ( anyelement, text, text ); +ALTER EXTENSION pgtap ADD FUNCTION doesnt_match ( anyelement, text ); +ALTER EXTENSION pgtap ADD FUNCTION doesnt_imatch ( anyelement, text, text ); +ALTER EXTENSION pgtap ADD FUNCTION doesnt_imatch ( anyelement, text ); +ALTER EXTENSION pgtap ADD FUNCTION unalike ( anyelement, text, text ); +ALTER EXTENSION pgtap ADD FUNCTION unalike ( anyelement, text ); +ALTER EXTENSION pgtap ADD FUNCTION unialike ( anyelement, text, text ); +ALTER EXTENSION pgtap ADD FUNCTION unialike ( anyelement, text ); +ALTER EXTENSION pgtap ADD FUNCTION cmp_ok (anyelement, text, anyelement, text); +ALTER EXTENSION pgtap ADD FUNCTION cmp_ok (anyelement, text, anyelement); +ALTER EXTENSION pgtap ADD FUNCTION pass ( text ); +ALTER EXTENSION pgtap ADD FUNCTION pass (); +ALTER EXTENSION pgtap ADD FUNCTION fail ( text ); +ALTER EXTENSION pgtap ADD FUNCTION fail (); +ALTER EXTENSION pgtap ADD FUNCTION todo ( why text, how_many int ); +ALTER EXTENSION pgtap ADD FUNCTION todo ( how_many int, why text ); +ALTER EXTENSION pgtap ADD FUNCTION todo ( why text ); +ALTER EXTENSION pgtap ADD FUNCTION todo ( how_many int ); +ALTER EXTENSION pgtap ADD FUNCTION todo_start (text); +ALTER EXTENSION pgtap ADD FUNCTION todo_start (); +ALTER EXTENSION pgtap ADD FUNCTION in_todo (); +ALTER EXTENSION pgtap ADD FUNCTION todo_end (); +ALTER EXTENSION pgtap ADD FUNCTION _todo(); +ALTER EXTENSION pgtap ADD FUNCTION skip ( why text, how_many int ); +ALTER EXTENSION pgtap ADD FUNCTION skip ( text ); +ALTER EXTENSION pgtap ADD FUNCTION skip( int, text ); +ALTER EXTENSION pgtap ADD FUNCTION skip( int ); +ALTER EXTENSION pgtap ADD FUNCTION _query( TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION throws_ok ( TEXT, CHAR(5), TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION throws_ok ( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION throws_ok ( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION throws_ok ( TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION throws_ok ( TEXT, int4, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION throws_ok ( TEXT, int4, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION throws_ok ( TEXT, int4 ); +ALTER EXTENSION pgtap ADD FUNCTION lives_ok ( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION lives_ok ( TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION performs_ok ( TEXT, NUMERIC, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION performs_ok ( TEXT, NUMERIC ); +ALTER EXTENSION pgtap ADD FUNCTION performs_within ( TEXT, NUMERIC, NUMERIC, INT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION performs_within ( TEXT, NUMERIC, NUMERIC, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION performs_within ( TEXT, NUMERIC, NUMERIC, INT ); +ALTER EXTENSION pgtap ADD FUNCTION performs_within ( TEXT, NUMERIC, NUMERIC ); +ALTER EXTENSION pgtap ADD FUNCTION _rexists ( CHAR, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _rexists ( CHAR, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_table ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_table ( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_table ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_table ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_table ( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_table ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_view ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_view ( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_view ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_view ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_view ( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_view ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_sequence ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_sequence ( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_sequence ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_sequence ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_sequence ( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_sequence ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _cexists ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _cexists ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_column ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_column ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_column ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_column ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_column ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_column ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _col_is_null ( NAME, NAME, NAME, TEXT, bool ); +ALTER EXTENSION pgtap ADD FUNCTION _col_is_null ( NAME, NAME, TEXT, bool ); +ALTER EXTENSION pgtap ADD FUNCTION col_not_null ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_not_null ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_not_null ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_null ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_null ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_null ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION display_type ( OID, INTEGER ); +ALTER EXTENSION pgtap ADD FUNCTION display_type ( NAME, OID, INTEGER ); +ALTER EXTENSION pgtap ADD FUNCTION _get_col_type ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _get_col_type ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _get_col_ns_type ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _quote_ident_like(TEXT, TEXT); +ALTER EXTENSION pgtap ADD FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_type_is ( NAME, NAME, NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_type_is ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_type_is ( NAME, NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_type_is ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _has_def ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _has_def ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION col_has_default ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_has_default ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_has_default ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION col_hasnt_default ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_hasnt_default ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_hasnt_default ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _cdi ( NAME, NAME, NAME, anyelement, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _cdi ( NAME, NAME, anyelement, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _cdi ( NAME, NAME, anyelement ); +ALTER EXTENSION pgtap ADD FUNCTION col_default_is ( NAME, NAME, NAME, anyelement, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_default_is ( NAME, NAME, NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_default_is ( NAME, NAME, anyelement, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_default_is ( NAME, NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_default_is ( NAME, NAME, anyelement ); +ALTER EXTENSION pgtap ADD FUNCTION col_default_is ( NAME, NAME, text ); +ALTER EXTENSION pgtap ADD FUNCTION _hasc ( NAME, NAME, CHAR ); +ALTER EXTENSION pgtap ADD FUNCTION _hasc ( NAME, CHAR ); +ALTER EXTENSION pgtap ADD FUNCTION has_pk ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_pk ( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_pk ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_pk ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_pk ( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_pk ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _ident_array_to_string( name[], text ); +ALTER EXTENSION pgtap ADD FUNCTION _pg_sv_column_array( OID, SMALLINT[] ); +ALTER EXTENSION pgtap ADD FUNCTION _pg_sv_table_accessible( OID, OID ); +ALTER EXTENSION pgtap ADD VIEW pg_all_foreign_keys; +ALTER EXTENSION pgtap ADD FUNCTION _keys ( NAME, NAME, CHAR ); +ALTER EXTENSION pgtap ADD FUNCTION _keys ( NAME, CHAR ); +ALTER EXTENSION pgtap ADD FUNCTION _ckeys ( NAME, NAME, CHAR ); +ALTER EXTENSION pgtap ADD FUNCTION _ckeys ( NAME, CHAR ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_pk ( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_pk ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_pk ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_pk ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_pk ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_pk ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION col_isnt_pk ( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_isnt_pk ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_isnt_pk ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION col_isnt_pk ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_isnt_pk ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_isnt_pk ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_fk ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_fk ( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_fk ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_fk ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_fk ( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_fk ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _fkexists ( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _fkexists ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_fk ( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_fk ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_fk ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_fk ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_fk ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_fk ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION col_isnt_fk ( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_isnt_fk ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_isnt_fk ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION col_isnt_fk ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_isnt_fk ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_isnt_fk ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_unique ( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_unique ( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_unique ( TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_unique ( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_unique ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_unique ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_unique ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_unique ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_is_unique ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_check ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_check ( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_check ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION col_has_check ( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_has_check ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_has_check ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION col_has_check ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_has_check ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION col_has_check ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION fk_ok ( NAME, NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION fk_ok ( NAME, NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD VIEW tap_funky; +ALTER EXTENSION pgtap ADD FUNCTION _got_func ( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _got_func ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _got_func ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _got_func ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_function ( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_function( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION has_function ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_function( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_function ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_function( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION has_function( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_function( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_function ( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_function( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_function ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_function( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_function ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_function( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_function( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_function( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _pg_sv_type_array( OID[] ); +ALTER EXTENSION pgtap ADD FUNCTION can ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION can ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION can ( NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION can ( NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _ikeys( NAME, NAME, NAME); +ALTER EXTENSION pgtap ADD FUNCTION _ikeys( NAME, NAME); +ALTER EXTENSION pgtap ADD FUNCTION _have_index( NAME, NAME, NAME); +ALTER EXTENSION pgtap ADD FUNCTION _have_index( NAME, NAME); +ALTER EXTENSION pgtap ADD FUNCTION _iexpr( NAME, NAME, NAME); +ALTER EXTENSION pgtap ADD FUNCTION _iexpr( NAME, NAME); +ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, NAME, NAME[], text ); +ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, NAME, NAME, text ); +ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, NAME[], text ); +ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _is_schema( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, NAME, text ); +ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, text ); +ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_index ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_index ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_index ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_index ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION index_is_unique ( NAME, NAME, NAME, text ); +ALTER EXTENSION pgtap ADD FUNCTION index_is_unique ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION index_is_unique ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION index_is_unique ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION index_is_primary ( NAME, NAME, NAME, text ); +ALTER EXTENSION pgtap ADD FUNCTION index_is_primary ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION index_is_primary ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION index_is_primary ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION is_clustered ( NAME, NAME, NAME, text ); +ALTER EXTENSION pgtap ADD FUNCTION is_clustered ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION is_clustered ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION is_clustered ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION index_is_type ( NAME, NAME, NAME, NAME, text ); +ALTER EXTENSION pgtap ADD FUNCTION index_is_type ( NAME, NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION index_is_type ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION index_is_type ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _trig ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _trig ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_trigger ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_trigger ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_trigger ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_trigger ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_trigger ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_trigger ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_trigger ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_trigger ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME, text ); +ALTER EXTENSION pgtap ADD FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION trigger_is ( NAME, NAME, NAME, text ); +ALTER EXTENSION pgtap ADD FUNCTION trigger_is ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_schema( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_schema( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_schema( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_schema( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_tablespace( NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_tablespace( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_tablespace( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_tablespace( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_tablespace( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _has_type( NAME, NAME, CHAR[] ); +ALTER EXTENSION pgtap ADD FUNCTION _has_type( NAME, CHAR[] ); +ALTER EXTENSION pgtap ADD FUNCTION has_type( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_type( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_type( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_type( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_type( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_type( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_type( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_type( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_domain( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_domain( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_domain( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_domain( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_domain( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_domain( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_domain( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_domain( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_enum( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_enum( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_enum( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_enum( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_enum( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_enum( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_enum( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_enum( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION enum_has_labels( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION enum_has_labels( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION enum_has_labels( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION enum_has_labels( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _has_role( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_role( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_role( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_role( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_role( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _has_user( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_user( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_user( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_user( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_user( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _is_super( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION is_superuser( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION is_superuser( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION isnt_superuser( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION isnt_superuser( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _has_group( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_group( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_group( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_group( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_group( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _grolist ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION is_member_of( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION is_member_of( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION is_member_of( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION is_member_of( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _cmp_types(oid, name); +ALTER EXTENSION pgtap ADD FUNCTION _cast_exists ( NAME, NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _cast_exists ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _cast_exists ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_cast ( NAME, NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_cast ( NAME, NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_cast ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_cast ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_cast ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_cast ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_cast ( NAME, NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_cast ( NAME, NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_cast ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_cast ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_cast ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_cast ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _expand_context( char ); +ALTER EXTENSION pgtap ADD FUNCTION _get_context( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION cast_context_is( NAME, NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION cast_context_is( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _op_exists ( NAME, NAME, NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _op_exists ( NAME, NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _op_exists ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_operator ( NAME, NAME, NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_operator ( NAME, NAME, NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_operator ( NAME, NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_operator ( NAME, NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_operator ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_operator ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_leftop ( NAME, NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_leftop ( NAME, NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_leftop ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_leftop ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_leftop ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_leftop ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_rightop ( NAME, NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_rightop ( NAME, NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_rightop ( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_rightop ( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_rightop ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_rightop ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _are ( text, name[], name[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION tablespaces_are ( NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION tablespaces_are ( NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION schemas_are ( NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION schemas_are ( NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _extras ( CHAR, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _extras ( CHAR, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _missing ( CHAR, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _missing ( CHAR, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION tables_are ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION tables_are ( NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION tables_are ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION tables_are ( NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION views_are ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION views_are ( NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION views_are ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION views_are ( NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION sequences_are ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION sequences_are ( NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION sequences_are ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION sequences_are ( NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION functions_are ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION functions_are ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION functions_are ( NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION functions_are ( NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION indexes_are( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION indexes_are( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION indexes_are( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION indexes_are( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION users_are( NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION users_are( NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION groups_are( NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION groups_are( NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION languages_are( NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION languages_are( NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _is_trusted( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_language( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_language( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_language( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_language( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION language_is_trusted( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION language_is_trusted( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _opc_exists( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_opclass( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_opclass( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_opclass( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_opclass( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_opclass( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_opclass( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_opclass( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_opclass( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION opclasses_are ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION opclasses_are ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION opclasses_are ( NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION opclasses_are ( NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION rules_are( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION rules_are( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION rules_are( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION rules_are( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _is_instead( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _is_instead( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_rule( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_rule( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION has_rule( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION has_rule( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_rule( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_rule( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_rule( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION hasnt_rule( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION rule_is_instead( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION rule_is_instead( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION rule_is_instead( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION rule_is_instead( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _expand_on( char ); +ALTER EXTENSION pgtap ADD FUNCTION _contract_on( TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _rule_on( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _rule_on( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION rule_is_on( NAME, NAME, NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION rule_is_on( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION rule_is_on( NAME, NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION rule_is_on( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _nosuch( NAME, NAME, NAME[]); +ALTER EXTENSION pgtap ADD FUNCTION _func_compare( NAME, NAME, NAME[], anyelement, anyelement, TEXT); +ALTER EXTENSION pgtap ADD FUNCTION _func_compare( NAME, NAME, NAME[], boolean, TEXT); +ALTER EXTENSION pgtap ADD FUNCTION _func_compare( NAME, NAME, anyelement, anyelement, TEXT); +ALTER EXTENSION pgtap ADD FUNCTION _func_compare( NAME, NAME, boolean, TEXT); +ALTER EXTENSION pgtap ADD FUNCTION _lang ( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _lang ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _lang ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _lang ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION function_lang_is( NAME, NAME, NAME[], NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION function_lang_is( NAME, NAME, NAME[], NAME ); +ALTER EXTENSION pgtap ADD FUNCTION function_lang_is( NAME, NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION function_lang_is( NAME, NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION function_lang_is( NAME, NAME[], NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION function_lang_is( NAME, NAME[], NAME ); +ALTER EXTENSION pgtap ADD FUNCTION function_lang_is( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION function_lang_is( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _returns ( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _returns ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _returns ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _returns ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION function_returns( NAME, NAME, NAME[], TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION function_returns( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION function_returns( NAME, NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION function_returns( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION function_returns( NAME, NAME[], TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION function_returns( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION function_returns( NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION function_returns( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _definer ( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _definer ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _definer ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _definer ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION is_definer ( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION is_definer( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION is_definer ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION is_definer( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION is_definer ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION is_definer( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION is_definer( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION is_definer( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _agg ( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _agg ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _agg ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _agg ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION is_aggregate ( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION is_aggregate( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION is_aggregate ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION is_aggregate( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION is_aggregate ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION is_aggregate( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION is_aggregate( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION is_aggregate( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _strict ( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _strict ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _strict ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _strict ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION is_strict ( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION is_strict( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION is_strict ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION is_strict( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION is_strict ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION is_strict( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION is_strict( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION is_strict( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _expand_vol( char ); +ALTER EXTENSION pgtap ADD FUNCTION _refine_vol( text ); +ALTER EXTENSION pgtap ADD FUNCTION _vol ( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _vol ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _vol ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _vol ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION volatility_is( NAME, NAME, NAME[], TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION volatility_is( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION volatility_is( NAME, NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION volatility_is( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION volatility_is( NAME, NAME[], TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION volatility_is( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION volatility_is( NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION volatility_is( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT, BOOLEAN ); +ALTER EXTENSION pgtap ADD FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION check_test( TEXT, BOOLEAN, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION check_test( TEXT, BOOLEAN ); +ALTER EXTENSION pgtap ADD FUNCTION findfuncs( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION findfuncs( TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _runem( text[], boolean ); +ALTER EXTENSION pgtap ADD FUNCTION _is_verbose(); +ALTER EXTENSION pgtap ADD FUNCTION do_tap( name, text ); +ALTER EXTENSION pgtap ADD FUNCTION do_tap( name ); +ALTER EXTENSION pgtap ADD FUNCTION do_tap( text ); +ALTER EXTENSION pgtap ADD FUNCTION do_tap( ); +ALTER EXTENSION pgtap ADD FUNCTION _currtest(); +ALTER EXTENSION pgtap ADD FUNCTION _cleanup(); +ALTER EXTENSION pgtap ADD FUNCTION diag_test_name(TEXT); +ALTER EXTENSION pgtap ADD FUNCTION _runner( text[], text[], text[], text[], text[] ); +ALTER EXTENSION pgtap ADD FUNCTION runtests( NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION runtests( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION runtests( TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION runtests( ); +ALTER EXTENSION pgtap ADD FUNCTION _temptable ( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _temptable ( anyarray, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _temptypes( TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _docomp( TEXT, TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _relcomp( TEXT, anyarray, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION set_eq( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION set_eq( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION set_eq( TEXT, anyarray, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION set_eq( TEXT, anyarray ); +ALTER EXTENSION pgtap ADD FUNCTION bag_eq( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION bag_eq( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION bag_eq( TEXT, anyarray, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION bag_eq( TEXT, anyarray ); +ALTER EXTENSION pgtap ADD FUNCTION _do_ne( TEXT, TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _relne( TEXT, TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _relne( TEXT, anyarray, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION set_ne( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION set_ne( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION set_ne( TEXT, anyarray, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION set_ne( TEXT, anyarray ); +ALTER EXTENSION pgtap ADD FUNCTION bag_ne( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION bag_ne( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION bag_ne( TEXT, anyarray, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION bag_ne( TEXT, anyarray ); +ALTER EXTENSION pgtap ADD FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION set_has( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION set_has( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION bag_has( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION bag_has( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION set_hasnt( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION set_hasnt( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION bag_hasnt( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION bag_hasnt( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION results_eq( refcursor, refcursor, text ); +ALTER EXTENSION pgtap ADD FUNCTION results_eq( refcursor, refcursor ); +ALTER EXTENSION pgtap ADD FUNCTION results_eq( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION results_eq( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION results_eq( TEXT, anyarray, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION results_eq( TEXT, anyarray ); +ALTER EXTENSION pgtap ADD FUNCTION results_eq( TEXT, refcursor, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION results_eq( TEXT, refcursor ); +ALTER EXTENSION pgtap ADD FUNCTION results_eq( refcursor, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION results_eq( refcursor, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION results_eq( refcursor, anyarray, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION results_eq( refcursor, anyarray ); +ALTER EXTENSION pgtap ADD FUNCTION results_ne( refcursor, refcursor, text ); +ALTER EXTENSION pgtap ADD FUNCTION results_ne( refcursor, refcursor ); +ALTER EXTENSION pgtap ADD FUNCTION results_ne( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION results_ne( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION results_ne( TEXT, anyarray, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION results_ne( TEXT, anyarray ); +ALTER EXTENSION pgtap ADD FUNCTION results_ne( TEXT, refcursor, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION results_ne( TEXT, refcursor ); +ALTER EXTENSION pgtap ADD FUNCTION results_ne( refcursor, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION results_ne( refcursor, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION results_ne( refcursor, anyarray, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION results_ne( refcursor, anyarray ); +ALTER EXTENSION pgtap ADD FUNCTION isa_ok( anyelement, regtype, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION isa_ok( anyelement, regtype ); +ALTER EXTENSION pgtap ADD FUNCTION is_empty( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION is_empty( TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION collect_tap( VARIADIC text[] ); +ALTER EXTENSION pgtap ADD FUNCTION collect_tap( VARCHAR[] ); +ALTER EXTENSION pgtap ADD FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION throws_like ( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION throws_like ( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION throws_ilike ( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION throws_ilike ( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION throws_matching ( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION throws_matching ( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION throws_imatching ( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION throws_imatching ( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION roles_are( NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION roles_are( NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _types_are ( NAME, NAME[], TEXT, CHAR[] ); +ALTER EXTENSION pgtap ADD FUNCTION types_are ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION types_are ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _types_are ( NAME[], TEXT, CHAR[] ); +ALTER EXTENSION pgtap ADD FUNCTION types_are ( NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION types_are ( NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION domains_are ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION domains_are ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION domains_are ( NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION domains_are ( NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION enums_are ( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION enums_are ( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION enums_are ( NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION enums_are ( NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _dexists ( NAME, NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _dexists ( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION _get_dtype( NAME, TEXT, BOOLEAN ); +ALTER EXTENSION pgtap ADD FUNCTION _get_dtype( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION domain_type_is( NAME, TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION domain_type_is( NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION domain_type_is( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION domain_type_is( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION domain_type_isnt( NAME, TEXT, NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION domain_type_isnt( NAME, TEXT, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION domain_type_isnt( NAME, TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION domain_type_isnt( NAME, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION domain_type_isnt( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION domain_type_isnt( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION row_eq( TEXT, anyelement, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION row_eq( TEXT, anyelement ); +ALTER EXTENSION pgtap ADD FUNCTION triggers_are( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION triggers_are( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION triggers_are( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION triggers_are( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _areni ( text, text[], text[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION casts_are ( TEXT[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION casts_are ( TEXT[] ); +ALTER EXTENSION pgtap ADD FUNCTION display_oper ( NAME, OID ); +ALTER EXTENSION pgtap ADD FUNCTION operators_are( NAME, TEXT[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION operators_are ( NAME, TEXT[] ); +ALTER EXTENSION pgtap ADD FUNCTION operators_are( TEXT[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION operators_are ( TEXT[] ); +ALTER EXTENSION pgtap ADD FUNCTION columns_are( NAME, NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION columns_are( NAME, NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION columns_are( NAME, NAME[], TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION columns_are( NAME, NAME[] ); +ALTER EXTENSION pgtap ADD FUNCTION _get_db_owner( NAME ); +ALTER EXTENSION pgtap ADD FUNCTION db_owner_is ( NAME, NAME, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION db_owner_is ( NAME, NAME ); + +ALTER EXTENSION pgtap ADD PROCEDURE mock_func( TEXT, TEXT, TEXT, ANYELEMENT ); +ALTER EXTENSION pgtap ADD PROCEDURE fake_table( TEXT[], TEXT[], BOOL, BOOL ); +ALTER EXTENSION pgtap ADD PROCEDURE willing_count_calls_of( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION called_once( TEXT , TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION called_times( INT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION drop_prepared_statement( TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION print_table_as_json( TEXT, TEXT ); \ No newline at end of file diff --git a/pgtap.sql.in b/pgtap.sql.in new file mode 100644 index 000000000..ed94e3728 --- /dev/null +++ b/pgtap.sql.in @@ -0,0 +1,11870 @@ +-- This file defines pgTAP, a collection of functions for TAP-based unit +-- testing. It is distributed under the revised FreeBSD license. +-- +-- The home page for the pgTAP project is: +-- +-- https://pgtap.org/ + +CREATE OR REPLACE FUNCTION pg_version() +RETURNS text AS 'SELECT current_setting(''server_version'')' +LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE FUNCTION pg_version_num() +RETURNS integer AS $$ + SELECT current_setting('server_version_num')::integer; +$$ LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE FUNCTION os_name() +RETURNS TEXT AS 'SELECT ''__OS__''::text;' +LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE FUNCTION pgtap_version() +RETURNS NUMERIC AS 'SELECT __VERSION__;' +LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE FUNCTION plan( integer ) +RETURNS TEXT AS $$ +DECLARE + rcount INTEGER; +BEGIN + BEGIN + EXECUTE ' + CREATE TEMP SEQUENCE __tcache___id_seq; + CREATE TEMP TABLE __tcache__ ( + id INTEGER NOT NULL DEFAULT nextval(''__tcache___id_seq''), + label TEXT NOT NULL, + value INTEGER NOT NULL, + note TEXT NOT NULL DEFAULT '''' + ); + CREATE UNIQUE INDEX __tcache___key ON __tcache__(id); + GRANT ALL ON TABLE __tcache__ TO PUBLIC; + GRANT ALL ON TABLE __tcache___id_seq TO PUBLIC; + + CREATE TEMP SEQUENCE __tresults___numb_seq; + GRANT ALL ON TABLE __tresults___numb_seq TO PUBLIC; + '; + + EXCEPTION WHEN duplicate_table THEN + -- Raise an exception if there's already a plan. + EXECUTE 'SELECT TRUE FROM __tcache__ WHERE label = ''plan'''; + GET DIAGNOSTICS rcount = ROW_COUNT; + IF rcount > 0 THEN + RAISE EXCEPTION 'You tried to plan twice!'; + END IF; + END; + + -- Save the plan and return. + PERFORM _set('plan', $1 ); + PERFORM _set('failed', 0 ); + RETURN '1..' || $1; +END; +$$ LANGUAGE plpgsql strict; + +CREATE OR REPLACE FUNCTION no_plan() +RETURNS SETOF boolean AS $$ +BEGIN + PERFORM plan(0); + RETURN; +END; +$$ LANGUAGE plpgsql strict; + +CREATE OR REPLACE FUNCTION _get ( text ) +RETURNS integer AS $$ +DECLARE + ret integer; +BEGIN + EXECUTE 'SELECT value FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' INTO ret; + RETURN ret; +END; +$$ LANGUAGE plpgsql strict; + +CREATE OR REPLACE FUNCTION _get_latest ( text ) +RETURNS integer[] AS $$ +DECLARE + ret integer[]; +BEGIN + EXECUTE 'SELECT ARRAY[id, value] FROM __tcache__ WHERE label = ' || + quote_literal($1) || ' AND id = (SELECT MAX(id) FROM __tcache__ WHERE label = ' || + quote_literal($1) || ') LIMIT 1' INTO ret; + RETURN ret; +EXCEPTION WHEN undefined_table THEN + RAISE EXCEPTION 'You tried to run a test without a plan! Gotta have a plan'; +END; +$$ LANGUAGE plpgsql strict; + +CREATE OR REPLACE FUNCTION _get_latest ( text, integer ) +RETURNS integer AS $$ +DECLARE + ret integer; +BEGIN + EXECUTE 'SELECT MAX(id) FROM __tcache__ WHERE label = ' || + quote_literal($1) || ' AND value = ' || $2 INTO ret; + RETURN ret; +END; +$$ LANGUAGE plpgsql strict; + +CREATE OR REPLACE FUNCTION _get_note ( text ) +RETURNS text AS $$ +DECLARE + ret text; +BEGIN + EXECUTE 'SELECT note FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' INTO ret; + RETURN ret; +END; +$$ LANGUAGE plpgsql strict; + +CREATE OR REPLACE FUNCTION _get_note ( integer ) +RETURNS text AS $$ +DECLARE + ret text; +BEGIN + EXECUTE 'SELECT note FROM __tcache__ WHERE id = ' || $1 || ' LIMIT 1' INTO ret; + RETURN ret; +END; +$$ LANGUAGE plpgsql strict; + +CREATE OR REPLACE FUNCTION _set ( text, integer, text ) +RETURNS integer AS $$ +DECLARE + rcount integer; +BEGIN + EXECUTE 'UPDATE __tcache__ SET value = ' || $2 + || CASE WHEN $3 IS NULL THEN '' ELSE ', note = ' || quote_literal($3) END + || ' WHERE label = ' || quote_literal($1); + GET DIAGNOSTICS rcount = ROW_COUNT; + IF rcount = 0 THEN + RETURN _add( $1, $2, $3 ); + END IF; + RETURN $2; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _set ( text, integer ) +RETURNS integer AS $$ + SELECT _set($1, $2, '') +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _set ( integer, integer ) +RETURNS integer AS $$ +BEGIN + EXECUTE 'UPDATE __tcache__ SET value = ' || $2 + || ' WHERE id = ' || $1; + RETURN $2; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _add ( text, integer, text ) +RETURNS integer AS $$ +BEGIN + EXECUTE 'INSERT INTO __tcache__ (label, value, note) values (' || + quote_literal($1) || ', ' || $2 || ', ' || quote_literal(COALESCE($3, '')) || ')'; + RETURN $2; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _add ( text, integer ) +RETURNS integer AS $$ + SELECT _add($1, $2, '') +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION add_result ( bool, bool, text, text, text ) +RETURNS integer AS $$ +BEGIN + IF NOT $1 THEN PERFORM _set('failed', _get('failed') + 1); END IF; + RETURN nextval('__tresults___numb_seq'); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION num_failed () +RETURNS INTEGER AS $$ + SELECT _get('failed'); +$$ LANGUAGE SQL strict; + +CREATE OR REPLACE FUNCTION _finish (INTEGER, INTEGER, INTEGER, BOOLEAN DEFAULT NULL) +RETURNS SETOF TEXT AS $$ +DECLARE + curr_test ALIAS FOR $1; + exp_tests INTEGER := $2; + num_faild ALIAS FOR $3; + plural CHAR; + raise_ex ALIAS FOR $4; +BEGIN + plural := CASE exp_tests WHEN 1 THEN '' ELSE 's' END; + + IF curr_test IS NULL THEN + RAISE EXCEPTION '# No tests run!'; + END IF; + + IF exp_tests = 0 OR exp_tests IS NULL THEN + -- No plan. Output one now. + exp_tests = curr_test; + RETURN NEXT '1..' || exp_tests; + END IF; + + IF curr_test <> exp_tests THEN + RETURN NEXT diag( + 'Looks like you planned ' || exp_tests || ' test' || + plural || ' but ran ' || curr_test + ); + ELSIF num_faild > 0 THEN + IF raise_ex THEN + RAISE EXCEPTION '% test% failed of %', num_faild, CASE num_faild WHEN 1 THEN '' ELSE 's' END, exp_tests; + END IF; + RETURN NEXT diag( + 'Looks like you failed ' || num_faild || ' test' || + CASE num_faild WHEN 1 THEN '' ELSE 's' END + || ' of ' || exp_tests + ); + ELSE + + END IF; + RETURN; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION finish (exception_on_failure BOOLEAN DEFAULT NULL) +RETURNS SETOF TEXT AS $$ + SELECT * FROM _finish( + _get('curr_test'), + _get('plan'), + num_failed(), + $1 + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION diag ( msg text ) +RETURNS TEXT AS $$ + SELECT '# ' || replace( + replace( + replace( $1, E'\r\n', E'\n# ' ), + E'\n', + E'\n# ' + ), + E'\r', + E'\n# ' + ); +$$ LANGUAGE sql strict; + +CREATE OR REPLACE FUNCTION diag ( msg anyelement ) +RETURNS TEXT AS $$ + SELECT diag($1::text); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION diag( VARIADIC text[] ) +RETURNS TEXT AS $$ + SELECT diag(array_to_string($1, '')); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION diag( VARIADIC anyarray ) +RETURNS TEXT AS $$ + SELECT diag(array_to_string($1, '')); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION ok ( boolean, text ) +RETURNS TEXT AS $$ +DECLARE + aok ALIAS FOR $1; + descr text := $2; + test_num INTEGER; + todo_why TEXT; + ok BOOL; +BEGIN + todo_why := _todo(); + ok := CASE + WHEN aok = TRUE THEN aok + WHEN todo_why IS NULL THEN COALESCE(aok, false) + ELSE TRUE + END; + IF _get('plan') IS NULL THEN + RAISE EXCEPTION 'You tried to run a test without a plan! Gotta have a plan'; + END IF; + + test_num := add_result( + ok, + COALESCE(aok, false), + descr, + CASE WHEN todo_why IS NULL THEN '' ELSE 'todo' END, + COALESCE(todo_why, '') + ); + + RETURN (CASE aok WHEN TRUE THEN '' ELSE 'not ' END) + || 'ok ' || _set( 'curr_test', test_num ) + || CASE descr WHEN '' THEN '' ELSE COALESCE( ' - ' || substr(diag( descr ), 3), '' ) END + || COALESCE( ' ' || diag( 'TODO ' || todo_why ), '') + || CASE aok WHEN TRUE THEN '' ELSE E'\n' || + diag('Failed ' || + CASE WHEN todo_why IS NULL THEN '' ELSE '(TODO) ' END || + 'test ' || test_num || + CASE descr WHEN '' THEN '' ELSE COALESCE(': "' || descr || '"', '') END ) || + CASE WHEN aok IS NULL THEN E'\n' || diag(' (test result was NULL)') ELSE '' END + END; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION ok ( boolean ) +RETURNS TEXT AS $$ + SELECT ok( $1, NULL ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION is (anyelement, anyelement, text) +RETURNS TEXT AS $$ +DECLARE + result BOOLEAN; + output TEXT; +BEGIN + -- Would prefer $1 IS NOT DISTINCT FROM, but that's not supported by 8.1. + result := NOT $1 IS DISTINCT FROM $2; + output := ok( result, $3 ); + RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( + ' have: ' || CASE WHEN $1 IS NULL THEN 'NULL' ELSE $1::text END || + E'\n want: ' || CASE WHEN $2 IS NULL THEN 'NULL' ELSE $2::text END + ) END; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION is (anyelement, anyelement) +RETURNS TEXT AS $$ + SELECT is( $1, $2, NULL); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION isnt (anyelement, anyelement, text) +RETURNS TEXT AS $$ +DECLARE + result BOOLEAN; + output TEXT; +BEGIN + result := $1 IS DISTINCT FROM $2; + output := ok( result, $3 ); + RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( + ' have: ' || COALESCE( $1::text, 'NULL' ) || + E'\n want: anything else' + ) END; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION isnt (anyelement, anyelement) +RETURNS TEXT AS $$ + SELECT isnt( $1, $2, NULL); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _alike ( BOOLEAN, ANYELEMENT, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + result ALIAS FOR $1; + got ALIAS FOR $2; + rx ALIAS FOR $3; + descr ALIAS FOR $4; + output TEXT; +BEGIN + output := ok( result, descr ); + RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( + ' ' || COALESCE( quote_literal(got), 'NULL' ) || + E'\n doesn''t match: ' || COALESCE( quote_literal(rx), 'NULL' ) + ) END; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION matches ( anyelement, text, text ) +RETURNS TEXT AS $$ + SELECT _alike( $1 ~ $2, $1, $2, $3 ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION matches ( anyelement, text ) +RETURNS TEXT AS $$ + SELECT _alike( $1 ~ $2, $1, $2, NULL ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION imatches ( anyelement, text, text ) +RETURNS TEXT AS $$ + SELECT _alike( $1 ~* $2, $1, $2, $3 ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION imatches ( anyelement, text ) +RETURNS TEXT AS $$ + SELECT _alike( $1 ~* $2, $1, $2, NULL ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION alike ( anyelement, text, text ) +RETURNS TEXT AS $$ + SELECT _alike( $1 ~~ $2, $1, $2, $3 ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION alike ( anyelement, text ) +RETURNS TEXT AS $$ + SELECT _alike( $1 ~~ $2, $1, $2, NULL ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ialike ( anyelement, text, text ) +RETURNS TEXT AS $$ + SELECT _alike( $1 ~~* $2, $1, $2, $3 ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ialike ( anyelement, text ) +RETURNS TEXT AS $$ + SELECT _alike( $1 ~~* $2, $1, $2, NULL ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _unalike ( BOOLEAN, ANYELEMENT, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + result ALIAS FOR $1; + got ALIAS FOR $2; + rx ALIAS FOR $3; + descr ALIAS FOR $4; + output TEXT; +BEGIN + output := ok( result, descr ); + RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( + ' ' || COALESCE( quote_literal(got), 'NULL' ) || + E'\n matches: ' || COALESCE( quote_literal(rx), 'NULL' ) + ) END; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION doesnt_match ( anyelement, text, text ) +RETURNS TEXT AS $$ + SELECT _unalike( $1 !~ $2, $1, $2, $3 ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION doesnt_match ( anyelement, text ) +RETURNS TEXT AS $$ + SELECT _unalike( $1 !~ $2, $1, $2, NULL ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION doesnt_imatch ( anyelement, text, text ) +RETURNS TEXT AS $$ + SELECT _unalike( $1 !~* $2, $1, $2, $3 ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION doesnt_imatch ( anyelement, text ) +RETURNS TEXT AS $$ + SELECT _unalike( $1 !~* $2, $1, $2, NULL ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION unalike ( anyelement, text, text ) +RETURNS TEXT AS $$ + SELECT _unalike( $1 !~~ $2, $1, $2, $3 ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION unalike ( anyelement, text ) +RETURNS TEXT AS $$ + SELECT _unalike( $1 !~~ $2, $1, $2, NULL ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION unialike ( anyelement, text, text ) +RETURNS TEXT AS $$ + SELECT _unalike( $1 !~~* $2, $1, $2, $3 ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION unialike ( anyelement, text ) +RETURNS TEXT AS $$ + SELECT _unalike( $1 !~~* $2, $1, $2, NULL ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION cmp_ok (anyelement, text, anyelement, text) +RETURNS TEXT AS $$ +DECLARE + have ALIAS FOR $1; + op ALIAS FOR $2; + want ALIAS FOR $3; + descr ALIAS FOR $4; + result BOOLEAN; + output TEXT; +BEGIN + EXECUTE 'SELECT ' || + COALESCE(quote_literal( have ), 'NULL') || '::' || pg_typeof(have) || ' ' + || op || ' ' || + COALESCE(quote_literal( want ), 'NULL') || '::' || pg_typeof(want) + INTO result; + output := ok( COALESCE(result, FALSE), descr ); + RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( + ' ' || COALESCE( quote_literal(have), 'NULL' ) || + E'\n ' || op || + E'\n ' || COALESCE( quote_literal(want), 'NULL' ) + ) END; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION cmp_ok (anyelement, text, anyelement) +RETURNS TEXT AS $$ + SELECT cmp_ok( $1, $2, $3, NULL ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION pass ( text ) +RETURNS TEXT AS $$ + SELECT ok( TRUE, $1 ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION pass () +RETURNS TEXT AS $$ + SELECT ok( TRUE, NULL ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION fail ( text ) +RETURNS TEXT AS $$ + SELECT ok( FALSE, $1 ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION fail () +RETURNS TEXT AS $$ + SELECT ok( FALSE, NULL ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION todo ( why text, how_many int ) +RETURNS SETOF BOOLEAN AS $$ +BEGIN + PERFORM _add('todo', COALESCE(how_many, 1), COALESCE(why, '')); + RETURN; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION todo ( how_many int, why text ) +RETURNS SETOF BOOLEAN AS $$ +BEGIN + PERFORM _add('todo', COALESCE(how_many, 1), COALESCE(why, '')); + RETURN; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION todo ( why text ) +RETURNS SETOF BOOLEAN AS $$ +BEGIN + PERFORM _add('todo', 1, COALESCE(why, '')); + RETURN; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION todo ( how_many int ) +RETURNS SETOF BOOLEAN AS $$ +BEGIN + PERFORM _add('todo', COALESCE(how_many, 1), ''); + RETURN; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION todo_start (text) +RETURNS SETOF BOOLEAN AS $$ +BEGIN + PERFORM _add('todo', -1, COALESCE($1, '')); + RETURN; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION todo_start () +RETURNS SETOF BOOLEAN AS $$ +BEGIN + PERFORM _add('todo', -1, ''); + RETURN; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION in_todo () +RETURNS BOOLEAN AS $$ +DECLARE + todos integer; +BEGIN + todos := _get('todo'); + RETURN CASE WHEN todos IS NULL THEN FALSE ELSE TRUE END; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION todo_end () +RETURNS SETOF BOOLEAN AS $$ +DECLARE + id integer; +BEGIN + id := _get_latest( 'todo', -1 ); + IF id IS NULL THEN + RAISE EXCEPTION 'todo_end() called without todo_start()'; + END IF; + EXECUTE 'DELETE FROM __tcache__ WHERE id = ' || id; + RETURN; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _todo() +RETURNS TEXT AS $$ +DECLARE + todos INT[]; + note text; +BEGIN + -- Get the latest id and value, because todo() might have been called + -- again before the todos ran out for the first call to todo(). This + -- allows them to nest. + todos := _get_latest('todo'); + IF todos IS NULL THEN + -- No todos. + RETURN NULL; + END IF; + IF todos[2] = 0 THEN + -- Todos depleted. Clean up. + EXECUTE 'DELETE FROM __tcache__ WHERE id = ' || todos[1]; + RETURN NULL; + END IF; + -- Decrement the count of counted todos and return the reason. + IF todos[2] <> -1 THEN + PERFORM _set(todos[1], todos[2] - 1); + END IF; + note := _get_note(todos[1]); + + IF todos[2] = 1 THEN + -- This was the last todo, so delete the record. + EXECUTE 'DELETE FROM __tcache__ WHERE id = ' || todos[1]; + END IF; + + RETURN note; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION skip ( why text, how_many int ) +RETURNS TEXT AS $$ +DECLARE + output TEXT[]; +BEGIN + output := '{}'; + FOR i IN 1..how_many LOOP + output = array_append( + output, + ok( TRUE ) || ' ' || diag( 'SKIP' || COALESCE( ' ' || why, '') ) + ); + END LOOP; + RETURN array_to_string(output, E'\n'); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION skip ( text ) +RETURNS TEXT AS $$ + SELECT ok( TRUE ) || ' ' || diag( 'SKIP' || COALESCE(' ' || $1, '') ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION skip( int, text ) +RETURNS TEXT AS 'SELECT skip($2, $1)' +LANGUAGE sql; + +CREATE OR REPLACE FUNCTION skip( int ) +RETURNS TEXT AS 'SELECT skip(NULL, $1)' +LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _query( TEXT ) +RETURNS TEXT AS $$ + SELECT CASE + WHEN $1 LIKE '"%' OR $1 !~ '[[:space:]]' THEN 'EXECUTE ' || $1 + ELSE $1 + END; +$$ LANGUAGE SQL; + +-- throws_ok ( sql, errcode, errmsg, description ) +CREATE OR REPLACE FUNCTION throws_ok ( TEXT, CHAR(5), TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + query TEXT := _query($1); + errcode ALIAS FOR $2; + errmsg ALIAS FOR $3; + desctext ALIAS FOR $4; + descr TEXT; +BEGIN + descr := COALESCE( + desctext, + 'threw ' || errcode || ': ' || errmsg, + 'threw ' || errcode, + 'threw ' || errmsg, + 'threw an exception' + ); + EXECUTE query; + RETURN ok( FALSE, descr ) || E'\n' || diag( + ' caught: no exception' || + E'\n wanted: ' || COALESCE( errcode, 'an exception' ) + ); +EXCEPTION WHEN OTHERS OR ASSERT_FAILURE THEN + IF (errcode IS NULL OR SQLSTATE = errcode) + AND ( errmsg IS NULL OR SQLERRM = errmsg) + THEN + -- The expected errcode and/or message was thrown. + RETURN ok( TRUE, descr ); + ELSE + -- This was not the expected errcode or errmsg. + RETURN ok( FALSE, descr ) || E'\n' || diag( + ' caught: ' || SQLSTATE || ': ' || SQLERRM || + E'\n wanted: ' || COALESCE( errcode, 'an exception' ) || + COALESCE( ': ' || errmsg, '') + ); + END IF; +END; +$$ LANGUAGE plpgsql; + +-- throws_ok ( sql, errcode, errmsg ) +-- throws_ok ( sql, errmsg, description ) +CREATE OR REPLACE FUNCTION throws_ok ( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +BEGIN + IF octet_length($2) = 5 THEN + RETURN throws_ok( $1, $2::char(5), $3, NULL ); + ELSE + RETURN throws_ok( $1, NULL, $2, $3 ); + END IF; +END; +$$ LANGUAGE plpgsql; + +-- throws_ok ( query, errcode ) +-- throws_ok ( query, errmsg ) +CREATE OR REPLACE FUNCTION throws_ok ( TEXT, TEXT ) +RETURNS TEXT AS $$ +BEGIN + IF octet_length($2) = 5 THEN + RETURN throws_ok( $1, $2::char(5), NULL, NULL ); + ELSE + RETURN throws_ok( $1, NULL, $2, NULL ); + END IF; +END; +$$ LANGUAGE plpgsql; + +-- throws_ok ( sql ) +CREATE OR REPLACE FUNCTION throws_ok ( TEXT ) +RETURNS TEXT AS $$ + SELECT throws_ok( $1, NULL, NULL, NULL ); +$$ LANGUAGE SQL; + +-- Magically cast integer error codes. +-- throws_ok ( sql, errcode, errmsg, description ) +CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT throws_ok( $1, $2::char(5), $3, $4 ); +$$ LANGUAGE SQL; + +-- throws_ok ( sql, errcode, errmsg ) +CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4, TEXT ) +RETURNS TEXT AS $$ + SELECT throws_ok( $1, $2::char(5), $3, NULL ); +$$ LANGUAGE SQL; + +-- throws_ok ( sql, errcode ) +CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4 ) +RETURNS TEXT AS $$ + SELECT throws_ok( $1, $2::char(5), NULL, NULL ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _error_diag( TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT COALESCE( + COALESCE( NULLIF($1, '') || ': ', '' ) || COALESCE( NULLIF($2, ''), '' ), + 'NO ERROR FOUND' + ) + || COALESCE(E'\n DETAIL: ' || nullif($3, ''), '') + || COALESCE(E'\n HINT: ' || nullif($4, ''), '') + || COALESCE(E'\n SCHEMA: ' || nullif($6, ''), '') + || COALESCE(E'\n TABLE: ' || nullif($7, ''), '') + || COALESCE(E'\n COLUMN: ' || nullif($8, ''), '') + || COALESCE(E'\n CONSTRAINT: ' || nullif($9, ''), '') + || COALESCE(E'\n TYPE: ' || nullif($10, ''), '') + -- We need to manually indent all the context lines + || COALESCE(E'\n CONTEXT:\n' + || regexp_replace(NULLIF( $5, ''), '^', ' ', 'gn' + ), ''); +$$ LANGUAGE sql IMMUTABLE; + +-- lives_ok( sql, description ) +CREATE OR REPLACE FUNCTION lives_ok ( TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + code TEXT := _query($1); + descr ALIAS FOR $2; + detail text; + hint text; + context text; + schname text; + tabname text; + colname text; + chkname text; + typname text; +BEGIN + EXECUTE code; + RETURN ok( TRUE, descr ); +EXCEPTION WHEN OTHERS OR ASSERT_FAILURE THEN + -- There should have been no exception. + GET STACKED DIAGNOSTICS + detail = PG_EXCEPTION_DETAIL, + hint = PG_EXCEPTION_HINT, + context = PG_EXCEPTION_CONTEXT, + schname = SCHEMA_NAME, + tabname = TABLE_NAME, + colname = COLUMN_NAME, + chkname = CONSTRAINT_NAME, + typname = PG_DATATYPE_NAME; + RETURN ok( FALSE, descr ) || E'\n' || diag( + ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) + ); +END; +$$ LANGUAGE plpgsql; + +-- lives_ok( sql ) +CREATE OR REPLACE FUNCTION lives_ok ( TEXT ) +RETURNS TEXT AS $$ + SELECT lives_ok( $1, NULL ); +$$ LANGUAGE SQL; + +-- performs_ok ( sql, milliseconds, description ) +CREATE OR REPLACE FUNCTION performs_ok ( TEXT, NUMERIC, TEXT ) +RETURNS TEXT AS $$ +DECLARE + query TEXT := _query($1); + max_time ALIAS FOR $2; + descr ALIAS FOR $3; + starts_at TEXT; + act_time NUMERIC; +BEGIN + starts_at := timeofday(); + EXECUTE query; + act_time := extract( millisecond from timeofday()::timestamptz - starts_at::timestamptz); + IF act_time < max_time THEN RETURN ok(TRUE, descr); END IF; + RETURN ok( FALSE, descr ) || E'\n' || diag( + ' runtime: ' || act_time || ' ms' || + E'\n exceeds: ' || max_time || ' ms' + ); +END; +$$ LANGUAGE plpgsql; + +-- performs_ok ( sql, milliseconds ) +CREATE OR REPLACE FUNCTION performs_ok ( TEXT, NUMERIC ) +RETURNS TEXT AS $$ + SELECT performs_ok( + $1, $2, 'Should run in less than ' || $2 || ' ms' + ); +$$ LANGUAGE sql; + +-- Convenience function to run a query many times and returns +-- the middle set of those times as defined by the last argument +-- e.g. _time_trials('SELECT 1', 100, 0.8) will execute 'SELECT 1' +-- 100 times, and return the execution times for the middle 80 runs +-- +-- I could have left this logic in performs_within, but I have +-- plans to hook into this function for other purposes outside +-- of pgTAP +CREATE TYPE _time_trial_type +AS (a_time NUMERIC); +CREATE OR REPLACE FUNCTION _time_trials(TEXT, INT, NUMERIC) +RETURNS SETOF _time_trial_type AS $$ +DECLARE + query TEXT := _query($1); + iterations ALIAS FOR $2; + return_percent ALIAS FOR $3; + start_time TEXT; + act_time NUMERIC; + times NUMERIC[]; + offset_it INT; + limit_it INT; + offset_percent NUMERIC; + a_time _time_trial_type; +BEGIN + -- Execute the query over and over + FOR i IN 1..iterations LOOP + start_time := timeofday(); + EXECUTE query; + -- Store the execution time for the run in an array of times + times[i] := extract(millisecond from timeofday()::timestamptz - start_time::timestamptz); + END LOOP; + offset_percent := (1.0 - return_percent) / 2.0; + -- Ensure that offset skips the bottom X% of runs, or set it to 0 + SELECT GREATEST((offset_percent * iterations)::int, 0) INTO offset_it; + -- Ensure that with limit the query to returning only the middle X% of runs + SELECT GREATEST((return_percent * iterations)::int, 1) INTO limit_it; + + FOR a_time IN SELECT times[i] + FROM generate_series(array_lower(times, 1), array_upper(times, 1)) i + ORDER BY 1 + OFFSET offset_it + LIMIT limit_it LOOP + RETURN NEXT a_time; + END LOOP; +END; +$$ LANGUAGE plpgsql; + +-- performs_within( sql, average_milliseconds, within, iterations, description ) +CREATE OR REPLACE FUNCTION performs_within(TEXT, NUMERIC, NUMERIC, INT, TEXT) +RETURNS TEXT AS $$ +DECLARE + query TEXT := _query($1); + expected_avg ALIAS FOR $2; + within ALIAS FOR $3; + iterations ALIAS FOR $4; + descr ALIAS FOR $5; + avg_time NUMERIC; +BEGIN + SELECT avg(a_time) FROM _time_trials(query, iterations, 0.8) t1 INTO avg_time; + IF abs(avg_time - expected_avg) < within THEN RETURN ok(TRUE, descr); END IF; + RETURN ok(FALSE, descr) || E'\n' || diag(' average runtime: ' || avg_time || ' ms' + || E'\n desired average: ' || expected_avg || ' +/- ' || within || ' ms' + ); +END; +$$ LANGUAGE plpgsql; + +-- performs_within( sql, average_milliseconds, within, iterations ) +CREATE OR REPLACE FUNCTION performs_within(TEXT, NUMERIC, NUMERIC, INT) +RETURNS TEXT AS $$ +SELECT performs_within( + $1, $2, $3, $4, + 'Should run within ' || $2 || ' +/- ' || $3 || ' ms'); +$$ LANGUAGE sql; +-- performs_within( sql, average_milliseconds, within, description ) +CREATE OR REPLACE FUNCTION performs_within(TEXT, NUMERIC, NUMERIC, TEXT) +RETURNS TEXT AS $$ +SELECT performs_within( + $1, $2, $3, 10, $4 + ); +$$ LANGUAGE sql; + +-- performs_within( sql, average_milliseconds, within ) +CREATE OR REPLACE FUNCTION performs_within(TEXT, NUMERIC, NUMERIC) +RETURNS TEXT AS $$ +SELECT performs_within( + $1, $2, $3, 10, + 'Should run within ' || $2 || ' +/- ' || $3 || ' ms'); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _relexists ( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + WHERE n.nspname = $1 + AND c.relname = $2 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _relexists ( NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_class c + WHERE pg_catalog.pg_table_is_visible(c.oid) + AND c.relname = $1 + ); +$$ LANGUAGE SQL; + +-- has_relation( schema, relation, description ) +CREATE OR REPLACE FUNCTION has_relation ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _relexists( $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- has_relation( relation, description ) +CREATE OR REPLACE FUNCTION has_relation ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _relexists( $1 ), $2 ); +$$ LANGUAGE SQL; + +-- has_relation( relation ) +CREATE OR REPLACE FUNCTION has_relation ( NAME ) +RETURNS TEXT AS $$ + SELECT has_relation( $1, 'Relation ' || quote_ident($1) || ' should exist' ); +$$ LANGUAGE SQL; + +-- hasnt_relation( schema, relation, description ) +CREATE OR REPLACE FUNCTION hasnt_relation ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _relexists( $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_relation( relation, description ) +CREATE OR REPLACE FUNCTION hasnt_relation ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _relexists( $1 ), $2 ); +$$ LANGUAGE SQL; + +-- hasnt_relation( relation ) +CREATE OR REPLACE FUNCTION hasnt_relation ( NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_relation( $1, 'Relation ' || quote_ident($1) || ' should not exist' ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _rexists ( CHAR[], NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + WHERE c.relkind = ANY($1) + AND n.nspname = $2 + AND c.relname = $3 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _rexists ( CHAR[], NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_class c + WHERE c.relkind = ANY($1) + AND pg_catalog.pg_table_is_visible(c.oid) + AND c.relname = $2 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _rexists ( CHAR, NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT _rexists(ARRAY[$1], $2, $3); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _rexists ( CHAR, NAME ) +RETURNS BOOLEAN AS $$ +SELECT _rexists(ARRAY[$1], $2); +$$ LANGUAGE SQL; + +-- has_table( schema, table, description ) +CREATE OR REPLACE FUNCTION has_table ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists( '{r,p}'::char[], $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- has_table( schema, table ) +CREATE OR REPLACE FUNCTION has_table ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _rexists( '{r,p}'::char[], $1, $2 ), + 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' + ); +$$ LANGUAGE SQL; + +-- has_table( table, description ) +CREATE OR REPLACE FUNCTION has_table ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists( '{r,p}'::char[], $1 ), $2 ); +$$ LANGUAGE SQL; + +-- has_table( table ) +CREATE OR REPLACE FUNCTION has_table ( NAME ) +RETURNS TEXT AS $$ + SELECT has_table( $1, 'Table ' || quote_ident($1) || ' should exist' ); +$$ LANGUAGE SQL; + +-- hasnt_table( schema, table, description ) +CREATE OR REPLACE FUNCTION hasnt_table ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists( '{r,p}'::char[], $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_table( schema, table ) +CREATE OR REPLACE FUNCTION hasnt_table ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _rexists( '{r,p}'::char[], $1, $2 ), + 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_table( table, description ) +CREATE OR REPLACE FUNCTION hasnt_table ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists( '{r,p}'::char[], $1 ), $2 ); +$$ LANGUAGE SQL; + +-- hasnt_table( table ) +CREATE OR REPLACE FUNCTION hasnt_table ( NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_table( $1, 'Table ' || quote_ident($1) || ' should not exist' ); +$$ LANGUAGE SQL; + +-- has_view( schema, view, description ) +CREATE OR REPLACE FUNCTION has_view ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists( 'v', $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- has_view( schema, view ) +CREATE OR REPLACE FUNCTION has_view ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT has_view ( + $1, $2, + 'View ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' + ); +$$ LANGUAGE SQL; + +-- has_view( view, description ) +CREATE OR REPLACE FUNCTION has_view ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists( 'v', $1 ), $2 ); +$$ LANGUAGE SQL; + +-- has_view( view ) +CREATE OR REPLACE FUNCTION has_view ( NAME ) +RETURNS TEXT AS $$ + SELECT has_view( $1, 'View ' || quote_ident($1) || ' should exist' ); +$$ LANGUAGE SQL; + +-- hasnt_view( schema, view, description ) +CREATE OR REPLACE FUNCTION hasnt_view ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists( 'v', $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_view( schema, table ) +CREATE OR REPLACE FUNCTION hasnt_view ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_view( $1, $2, + 'View ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' + ); +$$ LANGUAGE SQL; + + +-- hasnt_view( view, description ) +CREATE OR REPLACE FUNCTION hasnt_view ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists( 'v', $1 ), $2 ); +$$ LANGUAGE SQL; + +-- hasnt_view( view ) +CREATE OR REPLACE FUNCTION hasnt_view ( NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_view( $1, 'View ' || quote_ident($1) || ' should not exist' ); +$$ LANGUAGE SQL; + +-- has_sequence( schema, sequence, description ) +CREATE OR REPLACE FUNCTION has_sequence ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists( 'S', $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- has_sequence( schema, sequence ) +CREATE OR REPLACE FUNCTION has_sequence ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _rexists( 'S', $1, $2 ), + 'Sequence ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' + ); +$$ LANGUAGE SQL; + +-- has_sequence( sequence, description ) +CREATE OR REPLACE FUNCTION has_sequence ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists( 'S', $1 ), $2 ); +$$ LANGUAGE SQL; + +-- has_sequence( sequence ) +CREATE OR REPLACE FUNCTION has_sequence ( NAME ) +RETURNS TEXT AS $$ + SELECT has_sequence( $1, 'Sequence ' || quote_ident($1) || ' should exist' ); +$$ LANGUAGE SQL; + +-- hasnt_sequence( schema, sequence, description ) +CREATE OR REPLACE FUNCTION hasnt_sequence ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists( 'S', $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_sequence( sequence, description ) +CREATE OR REPLACE FUNCTION hasnt_sequence ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists( 'S', $1 ), $2 ); +$$ LANGUAGE SQL; + +-- hasnt_sequence( sequence ) +CREATE OR REPLACE FUNCTION hasnt_sequence ( NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_sequence( $1, 'Sequence ' || quote_ident($1) || ' should not exist' ); +$$ LANGUAGE SQL; + +-- has_foreign_table( schema, table, description ) +CREATE OR REPLACE FUNCTION has_foreign_table ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists( 'f', $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- has_foreign_table( schema, table ) +CREATE OR REPLACE FUNCTION has_foreign_table ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _rexists( 'f', $1, $2 ), + 'Foreign table ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' + ); +$$ LANGUAGE SQL; + +-- has_foreign_table( table, description ) +CREATE OR REPLACE FUNCTION has_foreign_table ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists( 'f', $1 ), $2 ); +$$ LANGUAGE SQL; + +-- has_foreign_table( table ) +CREATE OR REPLACE FUNCTION has_foreign_table ( NAME ) +RETURNS TEXT AS $$ + SELECT has_foreign_table( $1, 'Foreign table ' || quote_ident($1) || ' should exist' ); +$$ LANGUAGE SQL; + +-- hasnt_foreign_table( schema, table, description ) +CREATE OR REPLACE FUNCTION hasnt_foreign_table ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists( 'f', $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_foreign_table( schema, table ) +CREATE OR REPLACE FUNCTION hasnt_foreign_table ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _rexists( 'f', $1, $2 ), + 'Foreign table ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_foreign_table( table, description ) +CREATE OR REPLACE FUNCTION hasnt_foreign_table ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists( 'f', $1 ), $2 ); +$$ LANGUAGE SQL; + +-- hasnt_foreign_table( table ) +CREATE OR REPLACE FUNCTION hasnt_foreign_table ( NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_foreign_table( $1, 'Foreign table ' || quote_ident($1) || ' should not exist' ); +$$ LANGUAGE SQL; + +-- has_composite( schema, type, description ) +CREATE OR REPLACE FUNCTION has_composite ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists( 'c', $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- has_composite( schema, type ) +CREATE OR REPLACE FUNCTION has_composite ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT has_composite($1, $2, + 'Composite type ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' + ); +$$ LANGUAGE SQL; + +-- has_composite( type, description ) +CREATE OR REPLACE FUNCTION has_composite ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists( 'c', $1 ), $2 ); +$$ LANGUAGE SQL; + +-- has_composite( type ) +CREATE OR REPLACE FUNCTION has_composite ( NAME ) +RETURNS TEXT AS $$ + SELECT has_composite( $1, 'Composite type ' || quote_ident($1) || ' should exist' ); +$$ LANGUAGE SQL; + +-- hasnt_composite( schema, type, description ) +CREATE OR REPLACE FUNCTION hasnt_composite ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists( 'c', $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_composite( schema, type ) +CREATE OR REPLACE FUNCTION hasnt_composite ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_composite( + $1, $2, + 'Composite type ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_composite( type, description ) +CREATE OR REPLACE FUNCTION hasnt_composite ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists( 'c', $1 ), $2 ); +$$ LANGUAGE SQL; + +-- hasnt_composite( type ) +CREATE OR REPLACE FUNCTION hasnt_composite ( NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_composite( $1, 'Composite type ' || quote_ident($1) || ' should not exist' ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _cexists ( NAME, NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid + WHERE n.nspname = $1 + AND c.relname = $2 + AND a.attnum > 0 + AND NOT a.attisdropped + AND a.attname = $3 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _cexists ( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid + WHERE c.relname = $1 + AND pg_catalog.pg_table_is_visible(c.oid) + AND a.attnum > 0 + AND NOT a.attisdropped + AND a.attname = $2 + ); +$$ LANGUAGE SQL; + +-- has_column( schema, table, column, description ) +CREATE OR REPLACE FUNCTION has_column ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _cexists( $1, $2, $3 ), $4 ); +$$ LANGUAGE SQL; + +-- has_column( table, column, description ) +CREATE OR REPLACE FUNCTION has_column ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _cexists( $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- has_column( table, column ) +CREATE OR REPLACE FUNCTION has_column ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT has_column( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); +$$ LANGUAGE SQL; + +-- hasnt_column( schema, table, column, description ) +CREATE OR REPLACE FUNCTION hasnt_column ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _cexists( $1, $2, $3 ), $4 ); +$$ LANGUAGE SQL; + +-- hasnt_column( table, column, description ) +CREATE OR REPLACE FUNCTION hasnt_column ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _cexists( $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_column( table, column ) +CREATE OR REPLACE FUNCTION hasnt_column ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_column( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); +$$ LANGUAGE SQL; + +-- _col_is_null( schema, table, column, desc, null ) +CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, NAME, TEXT, bool ) +RETURNS TEXT AS $$ +DECLARE + qcol CONSTANT text := quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3); + c_desc CONSTANT text := coalesce( + $4, + 'Column ' || qcol || ' should ' + || CASE WHEN $5 THEN 'be NOT' ELSE 'allow' END || ' NULL' + ); +BEGIN + IF NOT _cexists( $1, $2, $3 ) THEN + RETURN fail( c_desc ) || E'\n' + || diag (' Column ' || qcol || ' does not exist' ); + END IF; + RETURN ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid + WHERE n.nspname = $1 + AND c.relname = $2 + AND a.attnum > 0 + AND NOT a.attisdropped + AND a.attname = $3 + AND a.attnotnull = $5 + ), c_desc + ); +END; +$$ LANGUAGE plpgsql; + +-- _col_is_null( table, column, desc, null ) +CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, TEXT, bool ) +RETURNS TEXT AS $$ +DECLARE + qcol CONSTANT text := quote_ident($1) || '.' || quote_ident($2); + c_desc CONSTANT text := coalesce( + $3, + 'Column ' || qcol || ' should ' + || CASE WHEN $4 THEN 'be NOT' ELSE 'allow' END || ' NULL' + ); +BEGIN + IF NOT _cexists( $1, $2 ) THEN + RETURN fail( c_desc ) || E'\n' + || diag (' Column ' || qcol || ' does not exist' ); + END IF; + RETURN ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid + WHERE pg_catalog.pg_table_is_visible(c.oid) + AND c.relname = $1 + AND a.attnum > 0 + AND NOT a.attisdropped + AND a.attname = $2 + AND a.attnotnull = $4 + ), c_desc + ); +END; +$$ LANGUAGE plpgsql; + +-- col_not_null( schema, table, column, description ) +-- col_not_null( schema, table, column ) +CREATE OR REPLACE FUNCTION col_not_null ( schema_name NAME, table_name NAME, column_name NAME, description TEXT DEFAULT NULL ) +RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, $3, $4, true ); +$$ LANGUAGE SQL; + +-- col_not_null( table, column, description ) +-- col_not_null( table, column ) +CREATE OR REPLACE FUNCTION col_not_null ( table_name NAME, column_name NAME, description TEXT DEFAULT NULL ) +RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, $3, true ); +$$ LANGUAGE SQL; + +-- col_is_null( schema, table, column, description ) +-- col_is_null( schema, table, column ) +CREATE OR REPLACE FUNCTION col_is_null ( schema_name NAME, table_name NAME, column_name NAME, description TEXT DEFAULT NULL ) +RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, $3, $4, false ); +$$ LANGUAGE SQL; + +-- col_is_null( table, column, description ) +-- col_is_null( table, column ) +CREATE OR REPLACE FUNCTION col_is_null ( table_name NAME, column_name NAME, description TEXT DEFAULT NULL ) +RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, $3, false ); +$$ LANGUAGE SQL; + + +CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid + WHERE n.nspname = $1 + AND c.relname = $2 + AND a.attname = $3 + AND attnum > 0 + AND NOT a.attisdropped +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) + FROM pg_catalog.pg_attribute a + JOIN pg_catalog.pg_class c ON a.attrelid = c.oid + WHERE pg_catalog.pg_table_is_visible(c.oid) + AND c.relname = $1 + AND a.attname = $2 + AND attnum > 0 + AND NOT a.attisdropped +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_col_ns_type ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + -- Always include the namespace. + SELECT CASE WHEN pg_catalog.pg_type_is_visible(t.oid) + THEN quote_ident(tn.nspname) || '.' + ELSE '' + END || pg_catalog.format_type(a.atttypid, a.atttypmod) + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid + JOIN pg_catalog.pg_type t ON a.atttypid = t.oid + JOIN pg_catalog.pg_namespace tn ON t.typnamespace = tn.oid + WHERE n.nspname = $1 + AND c.relname = $2 + AND a.attname = $3 + AND attnum > 0 + AND NOT a.attisdropped +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _typename ( NAME ) +RETURNS TEXT AS $$ +BEGIN RETURN $1::REGTYPE; +EXCEPTION WHEN undefined_object THEN RETURN $1; +END; +$$ LANGUAGE PLPGSQL STABLE; + +CREATE OR REPLACE FUNCTION format_type_string ( TEXT ) +RETURNS TEXT AS $$ +DECLARE + want_type TEXT := $1; +BEGIN + IF pg_version_num() >= 170000 THEN + -- to_regtypemod() in 17 allows easy and corret normalization. + RETURN format_type(to_regtype(want_type), to_regtypemod(want_type)); + END IF; + + IF want_type::regtype = 'interval'::regtype THEN + -- We cannot normlize interval types without to_regtypemod(), So + -- just return it as is. + RETURN want_type; + END IF; + + -- Use the typmodin functions to correctly normalize types. + DECLARE + typmodin_arg cstring[]; + typmodin_func regproc; + typmod int; + BEGIN + -- Extract type modifier from type declaration and format as cstring[] literal. + typmodin_arg := translate(substring(want_type FROM '[(][^")]+[)]'), '()', '{}'); + + -- Find typmodin function for want_type. + SELECT typmodin INTO typmodin_func + FROM pg_catalog.pg_type + WHERE oid = want_type::regtype; + + IF typmodin_func = 0 THEN + -- Easy: types without typemods. + RETURN format_type(want_type::regtype, null); + END IF; + + -- Get typemod via type-specific typmodin function. + EXECUTE format('SELECT %s(%L)', typmodin_func, typmodin_arg) INTO typmod; + RETURN format_type(want_type::regtype, typmod); + END; + EXCEPTION WHEN OTHERS THEN RETURN NULL; +END; +$$ LANGUAGE PLPGSQL STABLE; + +-- col_type_is( schema, table, column, schema, type, description ) +CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + have_type TEXT := _get_col_ns_type($1, $2, $3); + want_type TEXT; +BEGIN + IF have_type IS NULL THEN + RETURN fail( $6 ) || E'\n' || diag ( + ' Column ' || COALESCE(quote_ident($1) || '.', '') + || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' + ); + END IF; + + IF quote_ident($4) = ANY(current_schemas(true)) THEN + want_type := quote_ident($4) || '.' || format_type_string($5); + ELSE + want_type := format_type_string(quote_ident($4) || '.' || $5); + END IF; + + IF want_type IS NULL THEN + RETURN fail( $6 ) || E'\n' || diag ( + ' Type ' || quote_ident($4) || '.' || $5 || ' does not exist' + ); + END IF; + + IF have_type = want_type THEN + -- We're good to go. + RETURN ok( true, $6 ); + END IF; + + -- Wrong data type. tell 'em what we really got. + RETURN ok( false, $6 ) || E'\n' || diag( + ' have: ' || have_type || + E'\n want: ' || want_type + ); +END; +$$ LANGUAGE plpgsql; + +-- col_type_is( schema, table, column, schema, type ) +CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT col_type_is( $1, $2, $3, $4, $5, 'Column ' || quote_ident($1) || '.' || quote_ident($2) + || '.' || quote_ident($3) || ' should be type ' || quote_ident($4) || '.' || $5); +$$ LANGUAGE SQL; + +-- col_type_is( schema, table, column, type, description ) +CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + have_type TEXT; + want_type TEXT; +BEGIN + -- Get the data type. + IF $1 IS NULL THEN + have_type := _get_col_type($2, $3); + ELSE + have_type := _get_col_type($1, $2, $3); + END IF; + + IF have_type IS NULL THEN + RETURN fail( $5 ) || E'\n' || diag ( + ' Column ' || COALESCE(quote_ident($1) || '.', '') + || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' + ); + END IF; + + want_type := format_type_string($4); + IF want_type IS NULL THEN + RETURN fail( $5 ) || E'\n' || diag ( + ' Type ' || $4 || ' does not exist' + ); + END IF; + + IF have_type = want_type THEN + -- We're good to go. + RETURN ok( true, $5 ); + END IF; + + -- Wrong data type. tell 'em what we really got. + RETURN ok( false, $5 ) || E'\n' || diag( + ' have: ' || have_type || + E'\n want: ' || want_type + ); +END; +$$ LANGUAGE plpgsql; + +-- col_type_is( schema, table, column, type ) +CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT col_type_is( $1, $2, $3, $4, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' should be type ' || $4 ); +$$ LANGUAGE SQL; + +-- col_type_is( table, column, type, description ) +CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT col_type_is( NULL, $1, $2, $3, $4 ); +$$ LANGUAGE SQL; + +-- col_type_is( table, column, type ) +CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT col_type_is( $1, $2, $3, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should be type ' || $3 ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _has_def ( NAME, NAME, NAME ) +RETURNS boolean AS $$ + SELECT a.atthasdef + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid + WHERE n.nspname = $1 + AND c.relname = $2 + AND a.attnum > 0 + AND NOT a.attisdropped + AND a.attname = $3 +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _has_def ( NAME, NAME ) +RETURNS boolean AS $$ + SELECT a.atthasdef + FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid + WHERE c.relname = $1 + AND a.attnum > 0 + AND NOT a.attisdropped + AND a.attname = $2 + AND pg_catalog.pg_table_is_visible(c.oid) +$$ LANGUAGE sql; + +-- col_has_default( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_has_default ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +BEGIN + IF NOT _cexists( $1, $2, $3 ) THEN + RETURN fail( $4 ) || E'\n' + || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' ); + END IF; + RETURN ok( _has_def( $1, $2, $3 ), $4 ); +END +$$ LANGUAGE plpgsql; + +-- col_has_default( table, column, description ) +CREATE OR REPLACE FUNCTION col_has_default ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +BEGIN + IF NOT _cexists( $1, $2 ) THEN + RETURN fail( $3 ) || E'\n' + || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' ); + END IF; + RETURN ok( _has_def( $1, $2 ), $3 ); +END; +$$ LANGUAGE plpgsql; + +-- col_has_default( table, column ) +CREATE OR REPLACE FUNCTION col_has_default ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT col_has_default( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should have a default' ); +$$ LANGUAGE SQL; + +-- col_hasnt_default( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_hasnt_default ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +BEGIN + IF NOT _cexists( $1, $2, $3 ) THEN + RETURN fail( $4 ) || E'\n' + || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' ); + END IF; + RETURN ok( NOT _has_def( $1, $2, $3 ), $4 ); +END; +$$ LANGUAGE plpgsql; + +-- col_hasnt_default( table, column, description ) +CREATE OR REPLACE FUNCTION col_hasnt_default ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +BEGIN + IF NOT _cexists( $1, $2 ) THEN + RETURN fail( $3 ) || E'\n' + || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' ); + END IF; + RETURN ok( NOT _has_def( $1, $2 ), $3 ); +END; +$$ LANGUAGE plpgsql; + +-- col_hasnt_default( table, column ) +CREATE OR REPLACE FUNCTION col_hasnt_default ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT col_hasnt_default( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should not have a default' ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ) +RETURNS TEXT AS $$ +DECLARE + thing text; +BEGIN + -- Function, cast, or special SQL syntax. + IF $1 ~ '^[^'']+[(]' OR $1 ~ '[)]::[^'']+$' OR $1 = ANY('{CURRENT_CATALOG,CURRENT_ROLE,CURRENT_SCHEMA,CURRENT_USER,SESSION_USER,USER,CURRENT_DATE,CURRENT_TIME,CURRENT_TIMESTAMP,LOCALTIME,LOCALTIMESTAMP}') THEN + RETURN is( $1, $3, $4 ); + END IF; + + EXECUTE 'SELECT is(' + || COALESCE($1, 'NULL' || '::' || $2) || '::' || $2 || ', ' + || COALESCE(quote_literal($3), 'NULL') || '::' || $2 || ', ' + || COALESCE(quote_literal($4), 'NULL') + || ')' INTO thing; + RETURN thing; +END; +$$ LANGUAGE plpgsql; + +-- _cdi( schema, table, column, default, description ) +CREATE OR REPLACE FUNCTION _cdi ( NAME, NAME, NAME, anyelement, TEXT ) +RETURNS TEXT AS $$ +BEGIN + IF NOT _cexists( $1, $2, $3 ) THEN + RETURN fail( $5 ) || E'\n' + || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' ); + END IF; + + IF NOT _has_def( $1, $2, $3 ) THEN + RETURN fail( $5 ) || E'\n' + || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' has no default' ); + END IF; + + RETURN _def_is( + pg_catalog.pg_get_expr(d.adbin, d.adrelid), + pg_catalog.format_type(a.atttypid, a.atttypmod), + $4, $5 + ) + FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a, + pg_catalog.pg_attrdef d + WHERE n.oid = c.relnamespace + AND c.oid = a.attrelid + AND a.atthasdef + AND a.attrelid = d.adrelid + AND a.attnum = d.adnum + AND n.nspname = $1 + AND c.relname = $2 + AND a.attnum > 0 + AND NOT a.attisdropped + AND a.attname = $3; +END; +$$ LANGUAGE plpgsql; + +-- _cdi( table, column, default, description ) +CREATE OR REPLACE FUNCTION _cdi ( NAME, NAME, anyelement, TEXT ) +RETURNS TEXT AS $$ +BEGIN + IF NOT _cexists( $1, $2 ) THEN + RETURN fail( $4 ) || E'\n' + || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' ); + END IF; + + IF NOT _has_def( $1, $2 ) THEN + RETURN fail( $4 ) || E'\n' + || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || ' has no default' ); + END IF; + + RETURN _def_is( + pg_catalog.pg_get_expr(d.adbin, d.adrelid), + pg_catalog.format_type(a.atttypid, a.atttypmod), + $3, $4 + ) + FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, pg_catalog.pg_attrdef d + WHERE c.oid = a.attrelid + AND pg_table_is_visible(c.oid) + AND a.atthasdef + AND a.attrelid = d.adrelid + AND a.attnum = d.adnum + AND c.relname = $1 + AND a.attnum > 0 + AND NOT a.attisdropped + AND a.attname = $2; +END; +$$ LANGUAGE plpgsql; + +-- _cdi( table, column, default ) +CREATE OR REPLACE FUNCTION _cdi ( NAME, NAME, anyelement ) +RETURNS TEXT AS $$ + SELECT col_default_is( + $1, $2, $3, + 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should default to ' + || COALESCE( quote_literal($3), 'NULL') + ); +$$ LANGUAGE sql; + +-- col_default_is( schema, table, column, default, description ) +CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, NAME, anyelement, TEXT ) +RETURNS TEXT AS $$ + SELECT _cdi( $1, $2, $3, $4, $5 ); +$$ LANGUAGE sql; + +-- col_default_is( schema, table, column, default, description ) +CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _cdi( $1, $2, $3, $4, $5 ); +$$ LANGUAGE sql; + +-- col_default_is( table, column, default, description ) +CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, anyelement, TEXT ) +RETURNS TEXT AS $$ + SELECT _cdi( $1, $2, $3, $4 ); +$$ LANGUAGE sql; + +-- col_default_is( table, column, default, description ) +CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _cdi( $1, $2, $3, $4 ); +$$ LANGUAGE sql; + +-- col_default_is( table, column, default ) +CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, anyelement ) +RETURNS TEXT AS $$ + SELECT _cdi( $1, $2, $3 ); +$$ LANGUAGE sql; + +-- col_default_is( table, column, default::text ) +CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, text ) +RETURNS TEXT AS $$ + SELECT _cdi( $1, $2, $3 ); +$$ LANGUAGE sql; + +-- _hasc( schema, table, constraint_type ) +CREATE OR REPLACE FUNCTION _hasc ( NAME, NAME, CHAR ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON c.relnamespace = n.oid + JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid + WHERE n.nspname = $1 + AND c.relname = $2 + AND x.contype = $3 + ); +$$ LANGUAGE sql; + +-- _hasc( table, constraint_type ) +CREATE OR REPLACE FUNCTION _hasc ( NAME, CHAR ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid + WHERE pg_table_is_visible(c.oid) + AND c.relname = $1 + AND x.contype = $2 + ); +$$ LANGUAGE sql; + +-- has_pk( schema, table, description ) +CREATE OR REPLACE FUNCTION has_pk ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _hasc( $1, $2, 'p' ), $3 ); +$$ LANGUAGE sql; + +-- has_pk( schema, table ) +CREATE OR REPLACE FUNCTION has_pk ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT has_pk( $1, $2, 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have a primary key' ); +$$ LANGUAGE sql; + +-- has_pk( table, description ) +CREATE OR REPLACE FUNCTION has_pk ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _hasc( $1, 'p' ), $2 ); +$$ LANGUAGE sql; + +-- has_pk( table ) +CREATE OR REPLACE FUNCTION has_pk ( NAME ) +RETURNS TEXT AS $$ + SELECT has_pk( $1, 'Table ' || quote_ident($1) || ' should have a primary key' ); +$$ LANGUAGE sql; + +-- hasnt_pk( schema, table, description ) +CREATE OR REPLACE FUNCTION hasnt_pk ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _hasc( $1, $2, 'p' ), $3 ); +$$ LANGUAGE sql; + +-- hasnt_pk( table, description ) +CREATE OR REPLACE FUNCTION hasnt_pk ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _hasc( $1, 'p' ), $2 ); +$$ LANGUAGE sql; + +-- hasnt_pk( table ) +CREATE OR REPLACE FUNCTION hasnt_pk ( NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_pk( $1, 'Table ' || quote_ident($1) || ' should not have a primary key' ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _ident_array_to_string( name[], text ) +RETURNS text AS $$ + SELECT array_to_string(ARRAY( + SELECT quote_ident($1[i]) + FROM generate_series(1, array_upper($1, 1)) s(i) + ORDER BY i + ), $2); +$$ LANGUAGE SQL immutable; + +-- Borrowed from newsysviews: https://www.postgresql.org/ftp/projects/pgFoundry/newsysviews/ +CREATE OR REPLACE FUNCTION _pg_sv_column_array( OID, SMALLINT[] ) +RETURNS NAME[] AS $$ + SELECT ARRAY( + SELECT a.attname + FROM pg_catalog.pg_attribute a + JOIN generate_series(1, array_upper($2, 1)) s(i) ON a.attnum = $2[i] + WHERE attrelid = $1 + ORDER BY i + ) +$$ LANGUAGE SQL stable; + +-- Borrowed from newsysviews: https://www.postgresql.org/ftp/projects/pgFoundry/newsysviews/ +CREATE OR REPLACE FUNCTION _pg_sv_table_accessible( OID, OID ) +RETURNS BOOLEAN AS $$ + SELECT CASE WHEN has_schema_privilege($1, 'USAGE') THEN ( + has_table_privilege($2, 'SELECT') + OR has_table_privilege($2, 'INSERT') + or has_table_privilege($2, 'UPDATE') + OR has_table_privilege($2, 'DELETE') + OR has_table_privilege($2, 'RULE') + OR has_table_privilege($2, 'REFERENCES') + OR has_table_privilege($2, 'TRIGGER') + ) ELSE FALSE + END; +$$ LANGUAGE SQL immutable strict; + +-- Borrowed from newsysviews: https://www.postgresql.org/ftp/projects/pgFoundry/newsysviews/ +CREATE OR REPLACE VIEW pg_all_foreign_keys +AS + SELECT n1.nspname AS fk_schema_name, + c1.relname AS fk_table_name, + k1.conname AS fk_constraint_name, + c1.oid AS fk_table_oid, + _pg_sv_column_array(k1.conrelid,k1.conkey) AS fk_columns, + n2.nspname AS pk_schema_name, + c2.relname AS pk_table_name, + k2.conname AS pk_constraint_name, + c2.oid AS pk_table_oid, + ci.relname AS pk_index_name, + _pg_sv_column_array(k1.confrelid,k1.confkey) AS pk_columns, + CASE k1.confmatchtype WHEN 'f' THEN 'FULL' + WHEN 'p' THEN 'PARTIAL' + WHEN 'u' THEN 'NONE' + else null + END AS match_type, + CASE k1.confdeltype WHEN 'a' THEN 'NO ACTION' + WHEN 'c' THEN 'CASCADE' + WHEN 'd' THEN 'SET DEFAULT' + WHEN 'n' THEN 'SET NULL' + WHEN 'r' THEN 'RESTRICT' + else null + END AS on_delete, + CASE k1.confupdtype WHEN 'a' THEN 'NO ACTION' + WHEN 'c' THEN 'CASCADE' + WHEN 'd' THEN 'SET DEFAULT' + WHEN 'n' THEN 'SET NULL' + WHEN 'r' THEN 'RESTRICT' + ELSE NULL + END AS on_update, + k1.condeferrable AS is_deferrable, + k1.condeferred AS is_deferred + FROM pg_catalog.pg_constraint k1 + JOIN pg_catalog.pg_namespace n1 ON (n1.oid = k1.connamespace) + JOIN pg_catalog.pg_class c1 ON (c1.oid = k1.conrelid) + JOIN pg_catalog.pg_class c2 ON (c2.oid = k1.confrelid) + JOIN pg_catalog.pg_namespace n2 ON (n2.oid = c2.relnamespace) + JOIN pg_catalog.pg_depend d ON ( + d.classid = 'pg_constraint'::regclass + AND d.objid = k1.oid + AND d.objsubid = 0 + AND d.deptype = 'n' + AND d.refclassid = 'pg_class'::regclass + AND d.refobjsubid=0 + ) + JOIN pg_catalog.pg_class ci ON (ci.oid = d.refobjid AND ci.relkind = 'i') + LEFT JOIN pg_depend d2 ON ( + d2.classid = 'pg_class'::regclass + AND d2.objid = ci.oid + AND d2.objsubid = 0 + AND d2.deptype = 'i' + AND d2.refclassid = 'pg_constraint'::regclass + AND d2.refobjsubid = 0 + ) + LEFT JOIN pg_catalog.pg_constraint k2 ON ( + k2.oid = d2.refobjid + AND k2.contype IN ('p', 'u') + ) + WHERE k1.conrelid != 0 + AND k1.confrelid != 0 + AND k1.contype = 'f' + AND _pg_sv_table_accessible(n1.oid, c1.oid); + +-- _keys( schema, table, constraint_type ) +CREATE OR REPLACE FUNCTION _keys ( NAME, NAME, CHAR ) +RETURNS SETOF NAME[] AS $$ + SELECT _pg_sv_column_array(x.conrelid,x.conkey) -- name[] doesn't support collation + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid + WHERE n.nspname = $1 + AND c.relname = $2 + AND x.contype = $3 + ORDER BY 1 +$$ LANGUAGE sql; + +-- _keys( table, constraint_type ) +CREATE OR REPLACE FUNCTION _keys ( NAME, CHAR ) +RETURNS SETOF NAME[] AS $$ + SELECT _pg_sv_column_array(x.conrelid,x.conkey) -- name[] doesn't support collation + FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid + AND c.relname = $1 + AND x.contype = $2 + WHERE pg_catalog.pg_table_is_visible(c.oid) + ORDER BY 1 +$$ LANGUAGE sql; + +-- _ckeys( schema, table, constraint_type ) +CREATE OR REPLACE FUNCTION _ckeys ( NAME, NAME, CHAR ) +RETURNS NAME[] AS $$ + SELECT * FROM _keys($1, $2, $3) LIMIT 1; +$$ LANGUAGE sql; + +-- _ckeys( table, constraint_type ) +CREATE OR REPLACE FUNCTION _ckeys ( NAME, CHAR ) +RETURNS NAME[] AS $$ + SELECT * FROM _keys($1, $2) LIMIT 1; +$$ LANGUAGE sql; + +-- col_is_pk( schema, table, column[], description ) +CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT is( _ckeys( $1, $2, 'p' ), $3, $4 ); +$$ LANGUAGE sql; + +-- col_is_pk( schema, table, column[] ) +CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT col_is_pk( $1, $2, $3, 'Columns ' || quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string($3, ', ') || ') should be a primary key' ); +$$ LANGUAGE sql; + +-- col_is_pk( table, column[], description ) +CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT is( _ckeys( $1, 'p' ), $2, $3 ); +$$ LANGUAGE sql; + +-- col_is_pk( table, column[] ) +CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT col_is_pk( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should be a primary key' ); +$$ LANGUAGE sql; + +-- col_is_pk( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT col_is_pk( $1, $2, ARRAY[$3], $4 ); +$$ LANGUAGE sql; + +-- col_is_pk( schema, table, column ) +CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT col_is_pk( $1, $2, $3, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || '(' || quote_ident($3) || ') should be a primary key' ); +$$ LANGUAGE sql; + +-- col_is_pk( table, column, description ) +CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT col_is_pk( $1, ARRAY[$2], $3 ); +$$ LANGUAGE sql; + +-- col_is_pk( table, column ) +CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT col_is_pk( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should be a primary key' ); +$$ LANGUAGE sql; + +-- col_isnt_pk( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT isnt( _ckeys( $1, $2, 'p' ), $3, $4 ); +$$ LANGUAGE sql; + +-- col_isnt_pk( table, column, description ) +CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT isnt( _ckeys( $1, 'p' ), $2, $3 ); +$$ LANGUAGE sql; + +-- col_isnt_pk( table, column[] ) +CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT col_isnt_pk( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should not be a primary key' ); +$$ LANGUAGE sql; + +-- col_isnt_pk( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT col_isnt_pk( $1, $2, ARRAY[$3], $4 ); +$$ LANGUAGE sql; + +-- col_isnt_pk( table, column, description ) +CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT col_isnt_pk( $1, ARRAY[$2], $3 ); +$$ LANGUAGE sql; + +-- col_isnt_pk( table, column ) +CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT col_isnt_pk( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should not be a primary key' ); +$$ LANGUAGE sql; + +-- has_fk( schema, table, description ) +CREATE OR REPLACE FUNCTION has_fk ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _hasc( $1, $2, 'f' ), $3 ); +$$ LANGUAGE sql; + +-- has_fk( table, description ) +CREATE OR REPLACE FUNCTION has_fk ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _hasc( $1, 'f' ), $2 ); +$$ LANGUAGE sql; + +-- has_fk( table ) +CREATE OR REPLACE FUNCTION has_fk ( NAME ) +RETURNS TEXT AS $$ + SELECT has_fk( $1, 'Table ' || quote_ident($1) || ' should have a foreign key constraint' ); +$$ LANGUAGE sql; + +-- hasnt_fk( schema, table, description ) +CREATE OR REPLACE FUNCTION hasnt_fk ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _hasc( $1, $2, 'f' ), $3 ); +$$ LANGUAGE sql; + +-- hasnt_fk( table, description ) +CREATE OR REPLACE FUNCTION hasnt_fk ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _hasc( $1, 'f' ), $2 ); +$$ LANGUAGE sql; + +-- hasnt_fk( table ) +CREATE OR REPLACE FUNCTION hasnt_fk ( NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_fk( $1, 'Table ' || quote_ident($1) || ' should not have a foreign key constraint' ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _fkexists ( NAME, NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT TRUE + FROM pg_all_foreign_keys + WHERE fk_schema_name = $1 + AND quote_ident(fk_table_name) = quote_ident($2) + AND fk_columns = $3 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _fkexists ( NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT TRUE + FROM pg_all_foreign_keys + WHERE quote_ident(fk_table_name) = quote_ident($1) + AND pg_catalog.pg_table_is_visible(fk_table_oid) + AND fk_columns = $2 + ); +$$ LANGUAGE SQL; + +-- col_is_fk( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + names text[]; +BEGIN + IF _fkexists($1, $2, $3) THEN + RETURN pass( $4 ); + END IF; + + -- Try to show the columns. + SELECT ARRAY( + SELECT _ident_array_to_string(fk_columns, ', ') + FROM pg_all_foreign_keys + WHERE fk_schema_name = $1 + AND fk_table_name = $2 + ORDER BY fk_columns + ) INTO names; + + IF names[1] IS NOT NULL THEN + RETURN fail($4) || E'\n' || diag( + ' Table ' || quote_ident($1) || '.' || quote_ident($2) || E' has foreign key constraints on these columns:\n ' + || array_to_string( names, E'\n ' ) + ); + END IF; + + -- No FKs in this table. + RETURN fail($4) || E'\n' || diag( + ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' has no foreign key columns' + ); +END; +$$ LANGUAGE plpgsql; + +-- col_is_fk( table, column, description ) +CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + names text[]; +BEGIN + IF _fkexists($1, $2) THEN + RETURN pass( $3 ); + END IF; + + -- Try to show the columns. + SELECT ARRAY( + SELECT _ident_array_to_string(fk_columns, ', ') + FROM pg_all_foreign_keys + WHERE fk_table_name = $1 + ORDER BY fk_columns + ) INTO names; + + IF NAMES[1] IS NOT NULL THEN + RETURN fail($3) || E'\n' || diag( + ' Table ' || quote_ident($1) || E' has foreign key constraints on these columns:\n ' + || array_to_string( names, E'\n ' ) + ); + END IF; + + -- No FKs in this table. + RETURN fail($3) || E'\n' || diag( + ' Table ' || quote_ident($1) || ' has no foreign key columns' + ); +END; +$$ LANGUAGE plpgsql; + +-- col_is_fk( table, column[] ) +CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT col_is_fk( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should be a foreign key' ); +$$ LANGUAGE sql; + +-- col_is_fk( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT col_is_fk( $1, $2, ARRAY[$3], $4 ); +$$ LANGUAGE sql; + +-- col_is_fk( table, column, description ) +CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT col_is_fk( $1, ARRAY[$2], $3 ); +$$ LANGUAGE sql; + +-- col_is_fk( table, column ) +CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT col_is_fk( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should be a foreign key' ); +$$ LANGUAGE sql; + +-- col_isnt_fk( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _fkexists( $1, $2, $3 ), $4 ); +$$ LANGUAGE SQL; + +-- col_isnt_fk( table, column, description ) +CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _fkexists( $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- col_isnt_fk( table, column[] ) +CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT col_isnt_fk( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should not be a foreign key' ); +$$ LANGUAGE sql; + +-- col_isnt_fk( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT col_isnt_fk( $1, $2, ARRAY[$3], $4 ); +$$ LANGUAGE sql; + +-- col_isnt_fk( table, column, description ) +CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT col_isnt_fk( $1, ARRAY[$2], $3 ); +$$ LANGUAGE sql; + +-- col_isnt_fk( table, column ) +CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT col_isnt_fk( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should not be a foreign key' ); +$$ LANGUAGE sql; + +-- has_unique( schema, table, description ) +CREATE OR REPLACE FUNCTION has_unique ( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _hasc( $1, $2, 'u' ), $3 ); +$$ LANGUAGE sql; + +-- has_unique( table, description ) +CREATE OR REPLACE FUNCTION has_unique ( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _hasc( $1, 'u' ), $2 ); +$$ LANGUAGE sql; + +-- has_unique( table ) +CREATE OR REPLACE FUNCTION has_unique ( TEXT ) +RETURNS TEXT AS $$ + SELECT has_unique( $1, 'Table ' || quote_ident($1) || ' should have a unique constraint' ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + akey NAME[]; + keys TEXT[] := '{}'; + have TEXT; +BEGIN + FOR akey IN SELECT * FROM _keys($1, $2, $3) LOOP + IF akey = $4 THEN RETURN pass($5); END IF; + keys = keys || akey::text; + END LOOP; + IF array_upper(keys, 0) = 1 THEN + have := 'No ' || $6 || ' constraints'; + ELSE + have := array_to_string(keys, E'\n '); + END IF; + + RETURN fail($5) || E'\n' || diag( + ' have: ' || have + || E'\n want: ' || CASE WHEN $4 IS NULL THEN 'NULL' ELSE $4::text END + ); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + akey NAME[]; + keys TEXT[] := '{}'; + have TEXT; +BEGIN + FOR akey IN SELECT * FROM _keys($1, $2) LOOP + IF akey = $3 THEN RETURN pass($4); END IF; + keys = keys || akey::text; + END LOOP; + IF array_upper(keys, 0) = 1 THEN + have := 'No ' || $5 || ' constraints'; + ELSE + have := array_to_string(keys, E'\n '); + END IF; + + RETURN fail($4) || E'\n' || diag( + ' have: ' || have + || E'\n want: ' || CASE WHEN $3 IS NULL THEN 'NULL' ELSE $3::text END + ); +END; +$$ LANGUAGE plpgsql; + +-- col_is_unique( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _constraint( $1, $2, 'u', $3, $4, 'unique' ); +$$ LANGUAGE sql; + +-- col_is_unique( schema, table, column[] ) +CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT col_is_unique( $1, $2, $3, 'Columns ' || quote_ident($2) || '(' || _ident_array_to_string($3, ', ') || ') should have a unique constraint' ); +$$ LANGUAGE sql; + +-- col_is_unique( scheam, table, column ) +CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT col_is_unique( $1, $2, ARRAY[$3], 'Column ' || quote_ident($2) || '(' || quote_ident($3) || ') should have a unique constraint' ); +$$ LANGUAGE sql; + +-- col_is_unique( table, column, description ) +CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _constraint( $1, 'u', $2, $3, 'unique' ); +$$ LANGUAGE sql; + +-- col_is_unique( table, column[] ) +CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT col_is_unique( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should have a unique constraint' ); +$$ LANGUAGE sql; + +-- col_is_unique( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT col_is_unique( $1, $2, ARRAY[$3], $4 ); +$$ LANGUAGE sql; + +-- col_is_unique( table, column, description ) +CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT col_is_unique( $1, ARRAY[$2], $3 ); +$$ LANGUAGE sql; + +-- col_is_unique( table, column ) +CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT col_is_unique( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should have a unique constraint' ); +$$ LANGUAGE sql; + +-- has_check( schema, table, description ) +CREATE OR REPLACE FUNCTION has_check ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _hasc( $1, $2, 'c' ), $3 ); +$$ LANGUAGE sql; + +-- has_check( table, description ) +CREATE OR REPLACE FUNCTION has_check ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _hasc( $1, 'c' ), $2 ); +$$ LANGUAGE sql; + +-- has_check( table ) +CREATE OR REPLACE FUNCTION has_check ( NAME ) +RETURNS TEXT AS $$ + SELECT has_check( $1, 'Table ' || quote_ident($1) || ' should have a check constraint' ); +$$ LANGUAGE sql; + +-- col_has_check( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _constraint( $1, $2, 'c', $3, $4, 'check' ); +$$ LANGUAGE sql; + +-- col_has_check( table, column, description ) +CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _constraint( $1, 'c', $2, $3, 'check' ); +$$ LANGUAGE sql; + +-- col_has_check( table, column[] ) +CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT col_has_check( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should have a check constraint' ); +$$ LANGUAGE sql; + +-- col_has_check( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT col_has_check( $1, $2, ARRAY[$3], $4 ); +$$ LANGUAGE sql; + +-- col_has_check( table, column, description ) +CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT col_has_check( $1, ARRAY[$2], $3 ); +$$ LANGUAGE sql; + +-- col_has_check( table, column ) +CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT col_has_check( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should have a check constraint' ); +$$ LANGUAGE sql; + +-- fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description ) +CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + sch name; + tab name; + cols name[]; +BEGIN + SELECT pk_schema_name, pk_table_name, pk_columns + FROM pg_all_foreign_keys + WHERE fk_schema_name = $1 + AND fk_table_name = $2 + AND fk_columns = $3 + INTO sch, tab, cols; + + RETURN is( + -- have + quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( $3, ', ' ) + || ') REFERENCES ' || COALESCE ( sch || '.' || tab || '(' || _ident_array_to_string( cols, ', ' ) || ')', 'NOTHING' ), + -- want + quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( $3, ', ' ) + || ') REFERENCES ' || + $4 || '.' || $5 || '(' || _ident_array_to_string( $6, ', ' ) || ')', + $7 + ); +END; +$$ LANGUAGE plpgsql; + +-- fk_ok( fk_table, fk_column[], pk_table, pk_column[], description ) +CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + tab name; + cols name[]; +BEGIN + SELECT pk_table_name, pk_columns + FROM pg_all_foreign_keys + WHERE fk_table_name = $1 + AND fk_columns = $2 + AND pg_catalog.pg_table_is_visible(fk_table_oid) + INTO tab, cols; + + RETURN is( + -- have + $1 || '(' || _ident_array_to_string( $2, ', ' ) + || ') REFERENCES ' || COALESCE( tab || '(' || _ident_array_to_string( cols, ', ' ) || ')', 'NOTHING'), + -- want + $1 || '(' || _ident_array_to_string( $2, ', ' ) + || ') REFERENCES ' || + $3 || '(' || _ident_array_to_string( $4, ', ' ) || ')', + $5 + ); +END; +$$ LANGUAGE plpgsql; + +-- fk_ok( fk_schema, fk_table, fk_column[], fk_schema, pk_table, pk_column[] ) +CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT fk_ok( $1, $2, $3, $4, $5, $6, + quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( $3, ', ' ) + || ') should reference ' || + $4 || '.' || $5 || '(' || _ident_array_to_string( $6, ', ' ) || ')' + ); +$$ LANGUAGE sql; + +-- fk_ok( fk_table, fk_column[], pk_table, pk_column[] ) +CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT fk_ok( $1, $2, $3, $4, + $1 || '(' || _ident_array_to_string( $2, ', ' ) + || ') should reference ' || + $3 || '(' || _ident_array_to_string( $4, ', ' ) || ')' + ); +$$ LANGUAGE sql; + +-- fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column, description ) +CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT fk_ok( $1, $2, ARRAY[$3], $4, $5, ARRAY[$6], $7 ); +$$ LANGUAGE sql; + +-- fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column ) +CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT fk_ok( $1, $2, ARRAY[$3], $4, $5, ARRAY[$6] ); +$$ LANGUAGE sql; + +-- fk_ok( fk_table, fk_column, pk_table, pk_column, description ) +CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT fk_ok( $1, ARRAY[$2], $3, ARRAY[$4], $5 ); +$$ LANGUAGE sql; + +-- fk_ok( fk_table, fk_column, pk_table, pk_column ) +CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT fk_ok( $1, ARRAY[$2], $3, ARRAY[$4] ); +$$ LANGUAGE sql; + +/* + * tap_funky used to just be a simple view, but the problem with that is the + * definition of pg_proc changed in version 11. Thanks to how pg_dump (and + * hence pg_upgrade) works, this made it impossible to upgrade Postgres if + * pgTap was installed. In order to fix that, we need code that will actually + * work on both < PG11 and >= PG11. + */ +CREATE OR REPLACE FUNCTION _prokind( p_oid oid ) +RETURNS "char" AS $$ +BEGIN + IF pg_version_num() >= 110000 THEN + RETURN prokind FROM pg_catalog.pg_proc WHERE oid = p_oid; + ELSE + RETURN CASE WHEN proisagg THEN 'a' WHEN proiswindow THEN 'w' ELSE 'f' END + FROM pg_catalog.pg_proc WHERE oid = p_oid; + END IF; +END; +$$ LANGUAGE plpgsql STABLE; + +CREATE OR REPLACE VIEW tap_funky + AS SELECT p.oid AS oid, + n.nspname AS schema, + p.proname AS name, + pg_catalog.pg_get_userbyid(p.proowner) AS owner, + array_to_string(p.proargtypes::regtype[], ',') AS args, + CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END + || p.prorettype::regtype AS returns, + p.prolang AS langoid, + p.proisstrict AS is_strict, + _prokind(p.oid) AS kind, + p.prosecdef AS is_definer, + p.proretset AS returns_set, + p.provolatile::char AS volatility, + pg_catalog.pg_function_is_visible(p.oid) AS is_visible + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid +; + +CREATE OR REPLACE FUNCTION _funkargs ( NAME[] ) +RETURNS TEXT AS $$ +BEGIN + RETURN array_to_string($1::regtype[], ','); +EXCEPTION WHEN undefined_object THEN + RETURN array_to_string($1, ','); +END; +$$ LANGUAGE PLPGSQL STABLE; + +CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT TRUE + FROM tap_funky + WHERE schema = $1 + AND name = $2 + AND args = _funkargs($3) + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( SELECT TRUE FROM tap_funky WHERE schema = $1 AND name = $2 ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT TRUE + FROM tap_funky + WHERE name = $1 + AND args = _funkargs($2) + AND is_visible + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _got_func ( NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( SELECT TRUE FROM tap_funky WHERE name = $1 AND is_visible); +$$ LANGUAGE SQL; + +-- has_function( schema, function, args[], description ) +CREATE OR REPLACE FUNCTION has_function ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _got_func($1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- has_function( schema, function, args[] ) +CREATE OR REPLACE FUNCTION has_function( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + _got_func($1, $2, $3), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should exist' + ); +$$ LANGUAGE sql; + +-- has_function( schema, function, description ) +CREATE OR REPLACE FUNCTION has_function ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _got_func($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- has_function( schema, function ) +CREATE OR REPLACE FUNCTION has_function( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _got_func($1, $2), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should exist' + ); +$$ LANGUAGE sql; + +-- has_function( function, args[], description ) +CREATE OR REPLACE FUNCTION has_function ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _got_func($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- has_function( function, args[] ) +CREATE OR REPLACE FUNCTION has_function( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + _got_func($1, $2), + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should exist' + ); +$$ LANGUAGE sql; + +-- has_function( function, description ) +CREATE OR REPLACE FUNCTION has_function( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _got_func($1), $2 ); +$$ LANGUAGE sql; + +-- has_function( function ) +CREATE OR REPLACE FUNCTION has_function( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _got_func($1), 'Function ' || quote_ident($1) || '() should exist' ); +$$ LANGUAGE sql; + +-- hasnt_function( schema, function, args[], description ) +CREATE OR REPLACE FUNCTION hasnt_function ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _got_func($1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- hasnt_function( schema, function, args[] ) +CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _got_func($1, $2, $3), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should not exist' + ); +$$ LANGUAGE sql; + +-- hasnt_function( schema, function, description ) +CREATE OR REPLACE FUNCTION hasnt_function ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _got_func($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_function( schema, function ) +CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _got_func($1, $2), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not exist' + ); +$$ LANGUAGE sql; + +-- hasnt_function( function, args[], description ) +CREATE OR REPLACE FUNCTION hasnt_function ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _got_func($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_function( function, args[] ) +CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _got_func($1, $2), + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should not exist' + ); +$$ LANGUAGE sql; + +-- hasnt_function( function, description ) +CREATE OR REPLACE FUNCTION hasnt_function( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _got_func($1), $2 ); +$$ LANGUAGE sql; + +-- hasnt_function( function ) +CREATE OR REPLACE FUNCTION hasnt_function( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _got_func($1), 'Function ' || quote_ident($1) || '() should not exist' ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _pg_sv_type_array( OID[] ) +RETURNS NAME[] AS $$ + SELECT ARRAY( + SELECT t.typname + FROM pg_catalog.pg_type t + JOIN generate_series(1, array_upper($1, 1)) s(i) ON t.oid = $1[i] + ORDER BY i + ) +$$ LANGUAGE SQL stable; + +-- can( schema, functions[], description ) +CREATE OR REPLACE FUNCTION can ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + missing text[]; +BEGIN + SELECT ARRAY( + SELECT quote_ident($2[i]) + FROM generate_series(1, array_upper($2, 1)) s(i) + LEFT JOIN tap_funky ON name = $2[i] AND schema = $1 + WHERE oid IS NULL + GROUP BY $2[i], s.i + ORDER BY MIN(s.i) + ) INTO missing; + IF missing[1] IS NULL THEN + RETURN ok( true, $3 ); + END IF; + RETURN ok( false, $3 ) || E'\n' || diag( + ' ' || quote_ident($1) || '.' || + array_to_string( missing, E'() missing\n ' || quote_ident($1) || '.') || + '() missing' + ); +END; +$$ LANGUAGE plpgsql; + +-- can( schema, functions[] ) +CREATE OR REPLACE FUNCTION can ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT can( $1, $2, 'Schema ' || quote_ident($1) || ' can' ); +$$ LANGUAGE sql; + +-- can( functions[], description ) +CREATE OR REPLACE FUNCTION can ( NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + missing text[]; +BEGIN + SELECT ARRAY( + SELECT quote_ident($1[i]) + FROM generate_series(1, array_upper($1, 1)) s(i) + LEFT JOIN pg_catalog.pg_proc p + ON $1[i] = p.proname + AND pg_catalog.pg_function_is_visible(p.oid) + WHERE p.oid IS NULL + ORDER BY s.i + ) INTO missing; + IF missing[1] IS NULL THEN + RETURN ok( true, $2 ); + END IF; + RETURN ok( false, $2 ) || E'\n' || diag( + ' ' || + array_to_string( missing, E'() missing\n ') || + '() missing' + ); +END; +$$ LANGUAGE plpgsql; + +-- can( functions[] ) +CREATE OR REPLACE FUNCTION can ( NAME[] ) +RETURNS TEXT AS $$ + SELECT can( $1, 'Schema ' || _ident_array_to_string(current_schemas(true), ' or ') || ' can' ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _ikeys( NAME, NAME, NAME) +RETURNS TEXT[] AS $$ + SELECT ARRAY( + SELECT pg_catalog.pg_get_indexdef( ci.oid, s.i + 1, false) + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace + JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) + ON x.indkey[s.i] IS NOT NULL + WHERE ct.relname = $2 + AND ci.relname = $3 + AND n.nspname = $1 + ORDER BY s.i + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _ikeys( NAME, NAME) +RETURNS TEXT[] AS $$ + SELECT ARRAY( + SELECT pg_catalog.pg_get_indexdef( ci.oid, s.i + 1, false) + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) + ON x.indkey[s.i] IS NOT NULL + WHERE ct.relname = $1 + AND ci.relname = $2 + AND pg_catalog.pg_table_is_visible(ct.oid) + ORDER BY s.i + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _have_index( NAME, NAME, NAME) +RETURNS BOOLEAN AS $$ + SELECT EXISTS ( + SELECT TRUE + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace + WHERE n.nspname = $1 + AND ct.relname = $2 + AND ci.relname = $3 + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _have_index( NAME, NAME) +RETURNS BOOLEAN AS $$ + SELECT EXISTS ( + SELECT TRUE + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + WHERE ct.relname = $1 + AND ci.relname = $2 + AND pg_catalog.pg_table_is_visible(ct.oid) + ); +$$ LANGUAGE sql; + +-- has_index( schema, table, index, columns[], description ) +CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME[], text ) +RETURNS TEXT AS $$ +DECLARE + index_cols name[]; +BEGIN + index_cols := _ikeys($1, $2, $3 ); + + IF index_cols IS NULL OR index_cols = '{}'::name[] THEN + RETURN ok( false, $5 ) || E'\n' + || diag( 'Index ' || quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || ' not found'); + END IF; + + RETURN is( + quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || array_to_string( index_cols, ', ' ) || ')', + quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || array_to_string( $4, ', ' ) || ')', + $5 + ); +END; +$$ LANGUAGE plpgsql; + +-- has_index( schema, table, index, columns[] ) +CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT has_index( $1, $2, $3, $4, 'Index ' || quote_ident($3) || ' should exist' ); +$$ LANGUAGE sql; + +-- has_index( schema, table, index, column, description ) +CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME, text ) +RETURNS TEXT AS $$ + SELECT has_index( $1, $2, $3, ARRAY[$4], $5 ); +$$ LANGUAGE sql; + +-- has_index( schema, table, index, column ) +CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT has_index( $1, $2, $3, $4, 'Index ' || quote_ident($3) || ' should exist' ); +$$ LANGUAGE sql; + +-- has_index( table, index, columns[], description ) +CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME[], text ) +RETURNS TEXT AS $$ +DECLARE + index_cols name[]; +BEGIN + index_cols := _ikeys($1, $2 ); + + IF index_cols IS NULL OR index_cols = '{}'::name[] THEN + RETURN ok( false, $4 ) || E'\n' + || diag( 'Index ' || quote_ident($2) || ' ON ' || quote_ident($1) || ' not found'); + END IF; + + RETURN is( + quote_ident($2) || ' ON ' || quote_ident($1) || '(' || array_to_string( index_cols, ', ' ) || ')', + quote_ident($2) || ' ON ' || quote_ident($1) || '(' || array_to_string( $3, ', ' ) || ')', + $4 + ); +END; +$$ LANGUAGE plpgsql; + +-- has_index( table, index, columns[] ) +CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT has_index( $1, $2, $3, 'Index ' || quote_ident($2) || ' should exist' ); +$$ LANGUAGE sql; + +-- _is_schema( schema ) +CREATE OR REPLACE FUNCTION _is_schema( NAME ) +returns boolean AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_namespace + WHERE nspname = $1 + ); +$$ LANGUAGE sql; + +-- has_index( table, index, column/expression, description ) +-- has_index( schema, table, index, column/expression ) +CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, text ) +RETURNS TEXT AS $$ + SELECT CASE WHEN _is_schema( $1 ) THEN + -- Looking for schema.table index. + ok ( _have_index( $1, $2, $3 ), $4) + ELSE + -- Looking for particular columns. + has_index( $1, $2, ARRAY[$3], $4 ) + END; +$$ LANGUAGE sql; + +-- has_index( table, index, column/expression ) +-- has_index( schema, table, index ) +CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ +BEGIN + IF _is_schema($1) THEN + -- ( schema, table, index ) + RETURN ok( _have_index( $1, $2, $3 ), 'Index ' || quote_ident($3) || ' should exist' ); + ELSE + -- ( table, index, column/expression ) + RETURN has_index( $1, $2, $3, 'Index ' || quote_ident($2) || ' should exist' ); + END IF; +END; +$$ LANGUAGE plpgsql; + +-- has_index( table, index, description ) +CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, text ) +RETURNS TEXT AS $$ + SELECT CASE WHEN $3 LIKE '%(%' + THEN has_index( $1, $2, $3::name ) + ELSE ok( _have_index( $1, $2 ), $3 ) + END; +$$ LANGUAGE sql; + +-- has_index( table, index ) +CREATE OR REPLACE FUNCTION has_index ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( _have_index( $1, $2 ), 'Index ' || quote_ident($2) || ' should exist' ); +$$ LANGUAGE sql; + +-- hasnt_index( schema, table, index, description ) +CREATE OR REPLACE FUNCTION hasnt_index ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +BEGIN + RETURN ok( NOT _have_index( $1, $2, $3 ), $4 ); +END; +$$ LANGUAGE plpgSQL; + +-- hasnt_index( schema, table, index ) +CREATE OR REPLACE FUNCTION hasnt_index ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _have_index( $1, $2, $3 ), + 'Index ' || quote_ident($3) || ' should not exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_index( table, index, description ) +CREATE OR REPLACE FUNCTION hasnt_index ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _have_index( $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_index( table, index ) +CREATE OR REPLACE FUNCTION hasnt_index ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _have_index( $1, $2 ), + 'Index ' || quote_ident($2) || ' should not exist' + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _is_indexed( NAME, NAME, TEXT[] ) +RETURNS BOOL AS $$ +SELECT EXISTS( SELECT TRUE FROM ( + SELECT _ikeys(coalesce($1, n.nspname), $2, ci.relname) AS cols + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace + WHERE ($1 IS NULL OR n.nspname = $1) + AND ct.relname = $2 + ) icols + WHERE cols = $3 ) +$$ LANGUAGE sql; + +-- is_indexed( schema, table, columns[], description ) +CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _is_indexed($1, $2, $3), $4 ); +$$ LANGUAGE sql; + +-- is_indexed( schema, table, columns[] ) +CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + _is_indexed($1, $2, $3), + 'Should have an index on ' || quote_ident($1) || '.' || quote_ident($2) || '(' || array_to_string( $3, ', ' ) || ')' + ); +$$ LANGUAGE sql; + +-- is_indexed( table, columns[], description ) +CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _is_indexed(NULL, $1, $2), $3 ); +$$ LANGUAGE sql; + +-- is_indexed( table, columns[] ) +CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + _is_indexed(NULL, $1, $2), + 'Should have an index on ' || quote_ident($1) || '(' || array_to_string( $2, ', ' ) || ')' + ); +$$ LANGUAGE sql; + +-- is_indexed( schema, table, column, description ) +CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok ( _is_indexed( $1, $2, ARRAY[$3]::NAME[]), $4); +$$ LANGUAGE sql; + +-- is_indexed( schema, table, column ) +-- is_indexed( table, column, description ) +CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT CASE WHEN _is_schema( $1 ) THEN + -- Looking for schema.table index. + is_indexed( $1, $2, ARRAY[$3]::NAME[] ) + ELSE + -- Looking for particular columns. + is_indexed( $1, ARRAY[$2]::NAME[], $3 ) + END; +$$ LANGUAGE sql; + +-- is_indexed( table, column ) +CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok ( _is_indexed( NULL, $1, ARRAY[$2]::NAME[]) ); +$$ LANGUAGE sql; + +-- index_is_unique( schema, table, index, description ) +CREATE OR REPLACE FUNCTION index_is_unique ( NAME, NAME, NAME, text ) +RETURNS TEXT AS $$ +DECLARE + res boolean; +BEGIN + SELECT x.indisunique + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace + WHERE ct.relname = $2 + AND ci.relname = $3 + AND n.nspname = $1 + INTO res; + + RETURN ok( COALESCE(res, false), $4 ); +END; +$$ LANGUAGE plpgsql; + +-- index_is_unique( schema, table, index ) +CREATE OR REPLACE FUNCTION index_is_unique ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT index_is_unique( + $1, $2, $3, + 'Index ' || quote_ident($3) || ' should be unique' + ); +$$ LANGUAGE sql; + +-- index_is_unique( table, index ) +CREATE OR REPLACE FUNCTION index_is_unique ( NAME, NAME ) +RETURNS TEXT AS $$ +DECLARE + res boolean; +BEGIN + SELECT x.indisunique + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + WHERE ct.relname = $1 + AND ci.relname = $2 + AND pg_catalog.pg_table_is_visible(ct.oid) + INTO res; + + RETURN ok( + COALESCE(res, false), + 'Index ' || quote_ident($2) || ' should be unique' + ); +END; +$$ LANGUAGE plpgsql; + +-- index_is_unique( index ) +CREATE OR REPLACE FUNCTION index_is_unique ( NAME ) +RETURNS TEXT AS $$ +DECLARE + res boolean; +BEGIN + SELECT x.indisunique + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + WHERE ci.relname = $1 + AND pg_catalog.pg_table_is_visible(ct.oid) + INTO res; + + RETURN ok( + COALESCE(res, false), + 'Index ' || quote_ident($1) || ' should be unique' + ); +END; +$$ LANGUAGE plpgsql; + +-- index_is_primary( schema, table, index, description ) +CREATE OR REPLACE FUNCTION index_is_primary ( NAME, NAME, NAME, text ) +RETURNS TEXT AS $$ +DECLARE + res boolean; +BEGIN + SELECT x.indisprimary + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace + WHERE ct.relname = $2 + AND ci.relname = $3 + AND n.nspname = $1 + INTO res; + + RETURN ok( COALESCE(res, false), $4 ); +END; +$$ LANGUAGE plpgsql; + +-- index_is_primary( schema, table, index ) +CREATE OR REPLACE FUNCTION index_is_primary ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT index_is_primary( + $1, $2, $3, + 'Index ' || quote_ident($3) || ' should be on a primary key' + ); +$$ LANGUAGE sql; + +-- index_is_primary( table, index ) +CREATE OR REPLACE FUNCTION index_is_primary ( NAME, NAME ) +RETURNS TEXT AS $$ +DECLARE + res boolean; +BEGIN + SELECT x.indisprimary + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + WHERE ct.relname = $1 + AND ci.relname = $2 + AND pg_catalog.pg_table_is_visible(ct.oid) + INTO res; + + RETURN ok( + COALESCE(res, false), + 'Index ' || quote_ident($2) || ' should be on a primary key' + ); +END; +$$ LANGUAGE plpgsql; + +-- index_is_primary( index ) +CREATE OR REPLACE FUNCTION index_is_primary ( NAME ) +RETURNS TEXT AS $$ +DECLARE + res boolean; +BEGIN + SELECT x.indisprimary + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + WHERE ci.relname = $1 + AND pg_catalog.pg_table_is_visible(ct.oid) + INTO res; + + RETURN ok( + COALESCE(res, false), + 'Index ' || quote_ident($1) || ' should be on a primary key' + ); +END; +$$ LANGUAGE plpgsql; + +-- is_clustered( schema, table, index, description ) +CREATE OR REPLACE FUNCTION is_clustered ( NAME, NAME, NAME, text ) +RETURNS TEXT AS $$ +DECLARE + res boolean; +BEGIN + SELECT x.indisclustered + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace + WHERE ct.relname = $2 + AND ci.relname = $3 + AND n.nspname = $1 + INTO res; + + RETURN ok( COALESCE(res, false), $4 ); +END; +$$ LANGUAGE plpgsql; + +-- is_clustered( schema, table, index ) +CREATE OR REPLACE FUNCTION is_clustered ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT is_clustered( + $1, $2, $3, + 'Table ' || quote_ident($1) || '.' || quote_ident($2) || + ' should be clustered on index ' || quote_ident($3) + ); +$$ LANGUAGE sql; + +-- is_clustered( table, index ) +CREATE OR REPLACE FUNCTION is_clustered ( NAME, NAME ) +RETURNS TEXT AS $$ +DECLARE + res boolean; +BEGIN + SELECT x.indisclustered + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + WHERE ct.relname = $1 + AND ci.relname = $2 + INTO res; + + RETURN ok( + COALESCE(res, false), + 'Table ' || quote_ident($1) || ' should be clustered on index ' || quote_ident($2) + ); +END; +$$ LANGUAGE plpgsql; + +-- is_clustered( index ) +CREATE OR REPLACE FUNCTION is_clustered ( NAME ) +RETURNS TEXT AS $$ +DECLARE + res boolean; +BEGIN + SELECT x.indisclustered + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + WHERE ci.relname = $1 + INTO res; + + RETURN ok( + COALESCE(res, false), + 'Table should be clustered on index ' || quote_ident($1) + ); +END; +$$ LANGUAGE plpgsql; + +-- index_is_type( schema, table, index, type, description ) +CREATE OR REPLACE FUNCTION index_is_type ( NAME, NAME, NAME, NAME, text ) +RETURNS TEXT AS $$ +DECLARE + aname name; +BEGIN + SELECT am.amname + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace + JOIN pg_catalog.pg_am am ON ci.relam = am.oid + WHERE ct.relname = $2 + AND ci.relname = $3 + AND n.nspname = $1 + INTO aname; + + return is( aname, $4, $5 ); +END; +$$ LANGUAGE plpgsql; + +-- index_is_type( schema, table, index, type ) +CREATE OR REPLACE FUNCTION index_is_type ( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT index_is_type( + $1, $2, $3, $4, + 'Index ' || quote_ident($3) || ' should be a ' || quote_ident($4) || ' index' + ); +$$ LANGUAGE SQL; + +-- index_is_type( table, index, type ) +CREATE OR REPLACE FUNCTION index_is_type ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ +DECLARE + aname name; +BEGIN + SELECT am.amname + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN pg_catalog.pg_am am ON ci.relam = am.oid + WHERE ct.relname = $1 + AND ci.relname = $2 + INTO aname; + + return is( + aname, $3, + 'Index ' || quote_ident($2) || ' should be a ' || quote_ident($3) || ' index' + ); +END; +$$ LANGUAGE plpgsql; + +-- index_is_type( index, type ) +CREATE OR REPLACE FUNCTION index_is_type ( NAME, NAME ) +RETURNS TEXT AS $$ +DECLARE + aname name; +BEGIN + SELECT am.amname + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN pg_catalog.pg_am am ON ci.relam = am.oid + WHERE ci.relname = $1 + INTO aname; + + return is( + aname, $2, + 'Index ' || quote_ident($1) || ' should be a ' || quote_ident($2) || ' index' + ); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _trig ( NAME, NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_trigger t + JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid + JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = $1 + AND c.relname = $2 + AND t.tgname = $3 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _trig ( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_trigger t + JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid + WHERE c.relname = $1 + AND t.tgname = $2 + AND pg_catalog.pg_table_is_visible(c.oid) + ); +$$ LANGUAGE SQL; + +-- has_trigger( schema, table, trigger, description ) +CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _trig($1, $2, $3), $4); +$$ LANGUAGE SQL; + +-- has_trigger( schema, table, trigger ) +CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT has_trigger( + $1, $2, $3, + 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have trigger ' || quote_ident($3) + ); +$$ LANGUAGE sql; + +-- has_trigger( table, trigger, description ) +CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _trig($1, $2), $3); +$$ LANGUAGE sql; + +-- has_trigger( table, trigger ) +CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( _trig($1, $2), 'Table ' || quote_ident($1) || ' should have trigger ' || quote_ident($2)); +$$ LANGUAGE SQL; + +-- hasnt_trigger( schema, table, trigger, description ) +CREATE OR REPLACE FUNCTION hasnt_trigger ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _trig($1, $2, $3), $4); +$$ LANGUAGE SQL; + +-- hasnt_trigger( schema, table, trigger ) +CREATE OR REPLACE FUNCTION hasnt_trigger ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _trig($1, $2, $3), + 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should not have trigger ' || quote_ident($3) + ); +$$ LANGUAGE sql; + +-- hasnt_trigger( table, trigger, description ) +CREATE OR REPLACE FUNCTION hasnt_trigger ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _trig($1, $2), $3); +$$ LANGUAGE sql; + +-- hasnt_trigger( table, trigger ) +CREATE OR REPLACE FUNCTION hasnt_trigger ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _trig($1, $2), 'Table ' || quote_ident($1) || ' should not have trigger ' || quote_ident($2)); +$$ LANGUAGE SQL; + +-- trigger_is( schema, table, trigger, schema, function, description ) +CREATE OR REPLACE FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME, text ) +RETURNS TEXT AS $$ +DECLARE + pname text; +BEGIN + SELECT quote_ident(ni.nspname) || '.' || quote_ident(p.proname) + FROM pg_catalog.pg_trigger t + JOIN pg_catalog.pg_class ct ON ct.oid = t.tgrelid + JOIN pg_catalog.pg_namespace nt ON nt.oid = ct.relnamespace + JOIN pg_catalog.pg_proc p ON p.oid = t.tgfoid + JOIN pg_catalog.pg_namespace ni ON ni.oid = p.pronamespace + WHERE nt.nspname = $1 + AND ct.relname = $2 + AND t.tgname = $3 + INTO pname; + + RETURN is( pname, quote_ident($4) || '.' || quote_ident($5), $6 ); +END; +$$ LANGUAGE plpgsql; + +-- trigger_is( schema, table, trigger, schema, function ) +CREATE OR REPLACE FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT trigger_is( + $1, $2, $3, $4, $5, + 'Trigger ' || quote_ident($3) || ' should call ' || quote_ident($4) || '.' || quote_ident($5) || '()' + ); +$$ LANGUAGE sql; + +-- trigger_is( table, trigger, function, description ) +CREATE OR REPLACE FUNCTION trigger_is ( NAME, NAME, NAME, text ) +RETURNS TEXT AS $$ +DECLARE + pname text; +BEGIN + SELECT p.proname + FROM pg_catalog.pg_trigger t + JOIN pg_catalog.pg_class ct ON ct.oid = t.tgrelid + JOIN pg_catalog.pg_proc p ON p.oid = t.tgfoid + WHERE ct.relname = $1 + AND t.tgname = $2 + AND pg_catalog.pg_table_is_visible(ct.oid) + INTO pname; + + RETURN is( pname, $3::text, $4 ); +END; +$$ LANGUAGE plpgsql; + +-- trigger_is( table, trigger, function ) +CREATE OR REPLACE FUNCTION trigger_is ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT trigger_is( + $1, $2, $3, + 'Trigger ' || quote_ident($2) || ' should call ' || quote_ident($3) || '()' + ); +$$ LANGUAGE sql; + +-- has_schema( schema, description ) +CREATE OR REPLACE FUNCTION has_schema( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_namespace + WHERE nspname = $1 + ), $2 + ); +$$ LANGUAGE sql; + +-- has_schema( schema ) +CREATE OR REPLACE FUNCTION has_schema( NAME ) +RETURNS TEXT AS $$ + SELECT has_schema( $1, 'Schema ' || quote_ident($1) || ' should exist' ); +$$ LANGUAGE sql; + +-- hasnt_schema( schema, description ) +CREATE OR REPLACE FUNCTION hasnt_schema( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( + NOT EXISTS( + SELECT true + FROM pg_catalog.pg_namespace + WHERE nspname = $1 + ), $2 + ); +$$ LANGUAGE sql; + +-- hasnt_schema( schema ) +CREATE OR REPLACE FUNCTION hasnt_schema( NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_schema( $1, 'Schema ' || quote_ident($1) || ' should not exist' ); +$$ LANGUAGE sql; + +-- has_tablespace( tablespace, location, description ) +CREATE OR REPLACE FUNCTION has_tablespace( NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ +BEGIN + IF pg_version_num() >= 90200 THEN + RETURN ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_tablespace + WHERE spcname = $1 + AND pg_tablespace_location(oid) = $2 + ), $3 + ); + ELSE + RETURN ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_tablespace + WHERE spcname = $1 + AND spclocation = $2 + ), $3 + ); + END IF; +END; +$$ LANGUAGE plpgsql; + +-- has_tablespace( tablespace, description ) +CREATE OR REPLACE FUNCTION has_tablespace( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_tablespace + WHERE spcname = $1 + ), $2 + ); +$$ LANGUAGE sql; + +-- has_tablespace( tablespace ) +CREATE OR REPLACE FUNCTION has_tablespace( NAME ) +RETURNS TEXT AS $$ + SELECT has_tablespace( $1, 'Tablespace ' || quote_ident($1) || ' should exist' ); +$$ LANGUAGE sql; + +-- hasnt_tablespace( tablespace, description ) +CREATE OR REPLACE FUNCTION hasnt_tablespace( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( + NOT EXISTS( + SELECT true + FROM pg_catalog.pg_tablespace + WHERE spcname = $1 + ), $2 + ); +$$ LANGUAGE sql; + +-- hasnt_tablespace( tablespace ) +CREATE OR REPLACE FUNCTION hasnt_tablespace( NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_tablespace( $1, 'Tablespace ' || quote_ident($1) || ' should not exist' ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _has_type( NAME, NAME, CHAR[] ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_type t + JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid + WHERE t.typisdefined + AND n.nspname = $1 + AND t.typname = $2 + AND t.typtype = ANY( COALESCE($3, ARRAY['b', 'c', 'd', 'p', 'e']) ) + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _has_type( NAME, CHAR[] ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_type t + WHERE t.typisdefined + AND pg_catalog.pg_type_is_visible(t.oid) + AND t.typname = $1 + AND t.typtype = ANY( COALESCE($2, ARRAY['b', 'c', 'd', 'p', 'e']) ) + ); +$$ LANGUAGE sql; + +-- has_type( schema, type, description ) +CREATE OR REPLACE FUNCTION has_type( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _has_type( $1, $2, NULL ), $3 ); +$$ LANGUAGE sql; + +-- has_type( schema, type ) +CREATE OR REPLACE FUNCTION has_type( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT has_type( $1, $2, 'Type ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); +$$ LANGUAGE sql; + +-- has_type( type, description ) +CREATE OR REPLACE FUNCTION has_type( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _has_type( $1, NULL ), $2 ); +$$ LANGUAGE sql; + +-- has_type( type ) +CREATE OR REPLACE FUNCTION has_type( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _has_type( $1, NULL ), ('Type ' || quote_ident($1) || ' should exist')::text ); +$$ LANGUAGE sql; + +-- hasnt_type( schema, type, description ) +CREATE OR REPLACE FUNCTION hasnt_type( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _has_type( $1, $2, NULL ), $3 ); +$$ LANGUAGE sql; + +-- hasnt_type( schema, type ) +CREATE OR REPLACE FUNCTION hasnt_type( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_type( $1, $2, 'Type ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); +$$ LANGUAGE sql; + +-- hasnt_type( type, description ) +CREATE OR REPLACE FUNCTION hasnt_type( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _has_type( $1, NULL ), $2 ); +$$ LANGUAGE sql; + +-- hasnt_type( type ) +CREATE OR REPLACE FUNCTION hasnt_type( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _has_type( $1, NULL ), ('Type ' || quote_ident($1) || ' should not exist')::text ); +$$ LANGUAGE sql; + +-- has_domain( schema, domain, description ) +CREATE OR REPLACE FUNCTION has_domain( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _has_type( $1, $2, ARRAY['d'] ), $3 ); +$$ LANGUAGE sql; + +-- has_domain( schema, domain ) +CREATE OR REPLACE FUNCTION has_domain( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT has_domain( $1, $2, 'Domain ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); +$$ LANGUAGE sql; + +-- has_domain( domain, description ) +CREATE OR REPLACE FUNCTION has_domain( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _has_type( $1, ARRAY['d'] ), $2 ); +$$ LANGUAGE sql; + +-- has_domain( domain ) +CREATE OR REPLACE FUNCTION has_domain( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _has_type( $1, ARRAY['d'] ), ('Domain ' || quote_ident($1) || ' should exist')::text ); +$$ LANGUAGE sql; + +-- hasnt_domain( schema, domain, description ) +CREATE OR REPLACE FUNCTION hasnt_domain( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _has_type( $1, $2, ARRAY['d'] ), $3 ); +$$ LANGUAGE sql; + +-- hasnt_domain( schema, domain ) +CREATE OR REPLACE FUNCTION hasnt_domain( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_domain( $1, $2, 'Domain ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); +$$ LANGUAGE sql; + +-- hasnt_domain( domain, description ) +CREATE OR REPLACE FUNCTION hasnt_domain( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _has_type( $1, ARRAY['d'] ), $2 ); +$$ LANGUAGE sql; + +-- hasnt_domain( domain ) +CREATE OR REPLACE FUNCTION hasnt_domain( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _has_type( $1, ARRAY['d'] ), ('Domain ' || quote_ident($1) || ' should not exist')::text ); +$$ LANGUAGE sql; + +-- has_enum( schema, enum, description ) +CREATE OR REPLACE FUNCTION has_enum( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _has_type( $1, $2, ARRAY['e'] ), $3 ); +$$ LANGUAGE sql; + +-- has_enum( schema, enum ) +CREATE OR REPLACE FUNCTION has_enum( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT has_enum( $1, $2, 'Enum ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); +$$ LANGUAGE sql; + +-- has_enum( enum, description ) +CREATE OR REPLACE FUNCTION has_enum( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _has_type( $1, ARRAY['e'] ), $2 ); +$$ LANGUAGE sql; + +-- has_enum( enum ) +CREATE OR REPLACE FUNCTION has_enum( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should exist')::text ); +$$ LANGUAGE sql; + +-- hasnt_enum( schema, enum, description ) +CREATE OR REPLACE FUNCTION hasnt_enum( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _has_type( $1, $2, ARRAY['e'] ), $3 ); +$$ LANGUAGE sql; + +-- hasnt_enum( schema, enum ) +CREATE OR REPLACE FUNCTION hasnt_enum( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_enum( $1, $2, 'Enum ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); +$$ LANGUAGE sql; + +-- hasnt_enum( enum, description ) +CREATE OR REPLACE FUNCTION hasnt_enum( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _has_type( $1, ARRAY['e'] ), $2 ); +$$ LANGUAGE sql; + +-- hasnt_enum( enum ) +CREATE OR REPLACE FUNCTION hasnt_enum( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); +$$ LANGUAGE sql; + +-- enum_has_labels( schema, enum, labels, description ) +CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT is( + ARRAY( + SELECT e.enumlabel + FROM pg_catalog.pg_type t + JOIN pg_catalog.pg_enum e ON t.oid = e.enumtypid + JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid + WHERE t.typisdefined + AND n.nspname = $1 + AND t.typname = $2 + AND t.typtype = 'e' + ORDER BY e.enumsortorder + ), + $3, + $4 + ); +$$ LANGUAGE sql; + +-- enum_has_labels( schema, enum, labels ) +CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT enum_has_labels( + $1, $2, $3, + 'Enum ' || quote_ident($1) || '.' || quote_ident($2) || ' should have labels (' || array_to_string( $3, ', ' ) || ')' + ); +$$ LANGUAGE sql; + +-- enum_has_labels( enum, labels, description ) +CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT is( + ARRAY( + SELECT e.enumlabel + FROM pg_catalog.pg_type t + JOIN pg_catalog.pg_enum e ON t.oid = e.enumtypid + WHERE t.typisdefined + AND pg_catalog.pg_type_is_visible(t.oid) + AND t.typname = $1 + AND t.typtype = 'e' + ORDER BY e.enumsortorder + ), + $2, + $3 + ); +$$ LANGUAGE sql; + +-- enum_has_labels( enum, labels ) +CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT enum_has_labels( + $1, $2, + 'Enum ' || quote_ident($1) || ' should have labels (' || array_to_string( $2, ', ' ) || ')' + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _has_role( NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_roles + WHERE rolname = $1 + ); +$$ LANGUAGE sql STRICT; + +-- has_role( role, description ) +CREATE OR REPLACE FUNCTION has_role( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _has_role($1), $2 ); +$$ LANGUAGE sql; + +-- has_role( role ) +CREATE OR REPLACE FUNCTION has_role( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _has_role($1), 'Role ' || quote_ident($1) || ' should exist' ); +$$ LANGUAGE sql; + +-- hasnt_role( role, description ) +CREATE OR REPLACE FUNCTION hasnt_role( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _has_role($1), $2 ); +$$ LANGUAGE sql; + +-- hasnt_role( role ) +CREATE OR REPLACE FUNCTION hasnt_role( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _has_role($1), 'Role ' || quote_ident($1) || ' should not exist' ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _has_user( NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( SELECT true FROM pg_catalog.pg_user WHERE usename = $1); +$$ LANGUAGE sql STRICT; + +-- has_user( user, description ) +CREATE OR REPLACE FUNCTION has_user( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _has_user($1), $2 ); +$$ LANGUAGE sql; + +-- has_user( user ) +CREATE OR REPLACE FUNCTION has_user( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _has_user( $1 ), 'User ' || quote_ident($1) || ' should exist'); +$$ LANGUAGE sql; + +-- hasnt_user( user, description ) +CREATE OR REPLACE FUNCTION hasnt_user( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _has_user($1), $2 ); +$$ LANGUAGE sql; + +-- hasnt_user( user ) +CREATE OR REPLACE FUNCTION hasnt_user( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _has_user( $1 ), 'User ' || quote_ident($1) || ' should not exist'); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _is_super( NAME ) +RETURNS BOOLEAN AS $$ + SELECT rolsuper + FROM pg_catalog.pg_roles + WHERE rolname = $1 +$$ LANGUAGE sql STRICT; + +-- is_superuser( user, description ) +CREATE OR REPLACE FUNCTION is_superuser( NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + is_super boolean := _is_super($1); +BEGIN + IF is_super IS NULL THEN + RETURN fail( $2 ) || E'\n' || diag( ' User ' || quote_ident($1) || ' does not exist') ; + END IF; + RETURN ok( is_super, $2 ); +END; +$$ LANGUAGE plpgsql; + +-- is_superuser( user ) +CREATE OR REPLACE FUNCTION is_superuser( NAME ) +RETURNS TEXT AS $$ + SELECT is_superuser( $1, 'User ' || quote_ident($1) || ' should be a super user' ); +$$ LANGUAGE sql; + +-- isnt_superuser( user, description ) +CREATE OR REPLACE FUNCTION isnt_superuser( NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + is_super boolean := _is_super($1); +BEGIN + IF is_super IS NULL THEN + RETURN fail( $2 ) || E'\n' || diag( ' User ' || quote_ident($1) || ' does not exist') ; + END IF; + RETURN ok( NOT is_super, $2 ); +END; +$$ LANGUAGE plpgsql; + +-- isnt_superuser( user ) +CREATE OR REPLACE FUNCTION isnt_superuser( NAME ) +RETURNS TEXT AS $$ + SELECT isnt_superuser( $1, 'User ' || quote_ident($1) || ' should not be a super user' ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _has_group( NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_group + WHERE groname = $1 + ); +$$ LANGUAGE sql STRICT; + +-- has_group( group, description ) +CREATE OR REPLACE FUNCTION has_group( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _has_group($1), $2 ); +$$ LANGUAGE sql; + +-- has_group( group ) +CREATE OR REPLACE FUNCTION has_group( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _has_group($1), 'Group ' || quote_ident($1) || ' should exist' ); +$$ LANGUAGE sql; + +-- hasnt_group( group, description ) +CREATE OR REPLACE FUNCTION hasnt_group( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _has_group($1), $2 ); +$$ LANGUAGE sql; + +-- hasnt_group( group ) +CREATE OR REPLACE FUNCTION hasnt_group( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _has_group($1), 'Group ' || quote_ident($1) || ' should not exist' ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _grolist ( NAME ) +RETURNS oid[] AS $$ + SELECT ARRAY( + SELECT member + FROM pg_catalog.pg_auth_members m + JOIN pg_catalog.pg_roles r ON m.roleid = r.oid + WHERE r.rolname = $1 + ); +$$ LANGUAGE sql; + +-- is_member_of( role, members[], description ) +CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + missing text[]; +BEGIN + IF NOT _has_role($1) THEN + RETURN fail( $3 ) || E'\n' || diag ( + ' Role ' || quote_ident($1) || ' does not exist' + ); + END IF; + + SELECT ARRAY( + SELECT quote_ident($2[i]) + FROM generate_series(1, array_upper($2, 1)) s(i) + LEFT JOIN pg_catalog.pg_roles r ON rolname = $2[i] + WHERE r.oid IS NULL + OR NOT r.oid = ANY ( _grolist($1) ) + ORDER BY s.i + ) INTO missing; + IF missing[1] IS NULL THEN + RETURN ok( true, $3 ); + END IF; + RETURN ok( false, $3 ) || E'\n' || diag( + ' Members missing from the ' || quote_ident($1) || E' role:\n ' || + array_to_string( missing, E'\n ') + ); +END; +$$ LANGUAGE plpgsql; + +-- is_member_of( role, member, description ) +CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT is_member_of( $1, ARRAY[$2], $3 ); +$$ LANGUAGE SQL; + +-- is_member_of( role, members[] ) +CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT is_member_of( $1, $2, 'Should have members of role ' || quote_ident($1) ); +$$ LANGUAGE SQL; + +-- is_member_of( role, member ) +CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT is_member_of( $1, ARRAY[$2] ); +$$ LANGUAGE SQL; + +-- isnt_member_of( role, members[], description ) +CREATE OR REPLACE FUNCTION isnt_member_of( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + extra text[]; +BEGIN + IF NOT _has_role($1) THEN + RETURN fail( $3 ) || E'\n' || diag ( + ' Role ' || quote_ident($1) || ' does not exist' + ); + END IF; + + SELECT ARRAY( + SELECT quote_ident($2[i]) + FROM generate_series(1, array_upper($2, 1)) s(i) + LEFT JOIN pg_catalog.pg_roles r ON rolname = $2[i] + WHERE r.oid = ANY ( _grolist($1) ) + ORDER BY s.i + ) INTO extra; + IF extra[1] IS NULL THEN + RETURN ok( true, $3 ); + END IF; + RETURN ok( false, $3 ) || E'\n' || diag( + ' Members, who should not be in ' || quote_ident($1) || E' role:\n ' || + array_to_string( extra, E'\n ') + ); +END; +$$ LANGUAGE plpgsql; + +-- isnt_member_of( role, member, description ) +CREATE OR REPLACE FUNCTION isnt_member_of( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT isnt_member_of( $1, ARRAY[$2], $3 ); +$$ LANGUAGE SQL; + +-- isnt_member_of( role, members[] ) +CREATE OR REPLACE FUNCTION isnt_member_of( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT isnt_member_of( $1, $2, 'Should not have members of role ' || quote_ident($1) ); +$$ LANGUAGE SQL; + +-- isnt_member_of( role, member ) +CREATE OR REPLACE FUNCTION isnt_member_of( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT isnt_member_of( $1, ARRAY[$2] ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _cmp_types(oid, name) +RETURNS BOOLEAN AS $$ + SELECT pg_catalog.format_type($1, NULL) = _typename($2); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _cast_exists ( NAME, NAME, NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS ( + SELECT TRUE + FROM pg_catalog.pg_cast c + JOIN pg_catalog.pg_proc p ON c.castfunc = p.oid + JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid + WHERE _cmp_types(castsource, $1) + AND _cmp_types(casttarget, $2) + AND n.nspname = $3 + AND p.proname = $4 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _cast_exists ( NAME, NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS ( + SELECT TRUE + FROM pg_catalog.pg_cast c + JOIN pg_catalog.pg_proc p ON c.castfunc = p.oid + WHERE _cmp_types(castsource, $1) + AND _cmp_types(casttarget, $2) + AND p.proname = $3 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _cast_exists ( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS ( + SELECT TRUE + FROM pg_catalog.pg_cast c + WHERE _cmp_types(castsource, $1) + AND _cmp_types(casttarget, $2) + ); +$$ LANGUAGE SQL; + +-- has_cast( source_type, target_type, schema, function, description ) +CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _cast_exists( $1, $2, $3, $4 ), $5 ); +$$ LANGUAGE SQL; + +-- has_cast( source_type, target_type, schema, function ) +CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _cast_exists( $1, $2, $3, $4 ), + 'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2) + || ') WITH FUNCTION ' || quote_ident($3) + || '.' || quote_ident($4) || '() should exist' + ); +$$ LANGUAGE SQL; + +-- has_cast( source_type, target_type, function, description ) +CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _cast_exists( $1, $2, $3 ), $4 ); +$$ LANGUAGE SQL; + +-- has_cast( source_type, target_type, function ) +CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _cast_exists( $1, $2, $3 ), + 'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2) + || ') WITH FUNCTION ' || quote_ident($3) || '() should exist' + ); +$$ LANGUAGE SQL; + +-- has_cast( source_type, target_type, description ) +CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _cast_exists( $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- has_cast( source_type, target_type ) +CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _cast_exists( $1, $2 ), + 'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2) + || ') should exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_cast( source_type, target_type, schema, function, description ) +CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _cast_exists( $1, $2, $3, $4 ), $5 ); +$$ LANGUAGE SQL; + +-- hasnt_cast( source_type, target_type, schema, function ) +CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _cast_exists( $1, $2, $3, $4 ), + 'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2) + || ') WITH FUNCTION ' || quote_ident($3) + || '.' || quote_ident($4) || '() should not exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_cast( source_type, target_type, function, description ) +CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _cast_exists( $1, $2, $3 ), $4 ); +$$ LANGUAGE SQL; + +-- hasnt_cast( source_type, target_type, function ) +CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _cast_exists( $1, $2, $3 ), + 'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2) + || ') WITH FUNCTION ' || quote_ident($3) || '() should not exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_cast( source_type, target_type, description ) +CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _cast_exists( $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_cast( source_type, target_type ) +CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _cast_exists( $1, $2 ), + 'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2) + || ') should not exist' + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _expand_context( char ) +RETURNS text AS $$ + SELECT CASE $1 + WHEN 'i' THEN 'implicit' + WHEN 'a' THEN 'assignment' + WHEN 'e' THEN 'explicit' + ELSE 'unknown' END +$$ LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE FUNCTION _get_context( NAME, NAME ) +RETURNS "char" AS $$ + SELECT c.castcontext + FROM pg_catalog.pg_cast c + WHERE _cmp_types(castsource, $1) + AND _cmp_types(casttarget, $2) +$$ LANGUAGE SQL; + +-- cast_context_is( source_type, target_type, context, description ) +CREATE OR REPLACE FUNCTION cast_context_is( NAME, NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + want char = substring(LOWER($3) FROM 1 FOR 1); + have char := _get_context($1, $2); +BEGIN + IF have IS NOT NULL THEN + RETURN is( _expand_context(have), _expand_context(want), $4 ); + END IF; + + RETURN ok( false, $4 ) || E'\n' || diag( + ' Cast (' || quote_ident($1) || ' AS ' || quote_ident($2) + || ') does not exist' + ); +END; +$$ LANGUAGE plpgsql; + +-- cast_context_is( source_type, target_type, context ) +CREATE OR REPLACE FUNCTION cast_context_is( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT cast_context_is( + $1, $2, $3, + 'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2) + || ') context should be ' || _expand_context(substring(LOWER($3) FROM 1 FOR 1)) + ); +$$ LANGUAGE SQL; + +-- _op_exists( left_type, schema, name, right_type, return_type ) +CREATE OR REPLACE FUNCTION _op_exists ( NAME, NAME, NAME, NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS ( + SELECT TRUE + FROM pg_catalog.pg_operator o + JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid + WHERE n.nspname = $2 + AND o.oprname = $3 + AND CASE o.oprkind WHEN 'l' THEN $1 IS NULL + ELSE _cmp_types(o.oprleft, _typename($1)) END + AND CASE o.oprkind WHEN 'r' THEN $4 IS NULL + ELSE _cmp_types(o.oprright, _typename($4)) END + AND _cmp_types(o.oprresult, $5) + ); +$$ LANGUAGE SQL; + +-- _op_exists( left_type, name, right_type, return_type ) +CREATE OR REPLACE FUNCTION _op_exists ( NAME, NAME, NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS ( + SELECT TRUE + FROM pg_catalog.pg_operator o + WHERE pg_catalog.pg_operator_is_visible(o.oid) + AND o.oprname = $2 + AND CASE o.oprkind WHEN 'l' THEN $1 IS NULL + ELSE _cmp_types(o.oprleft, _typename($1)) END + AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL + ELSE _cmp_types(o.oprright, _typename($3)) END + AND _cmp_types(o.oprresult, $4) + ); +$$ LANGUAGE SQL; + +-- _op_exists( left_type, name, right_type ) +CREATE OR REPLACE FUNCTION _op_exists ( NAME, NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS ( + SELECT TRUE + FROM pg_catalog.pg_operator o + WHERE pg_catalog.pg_operator_is_visible(o.oid) + AND o.oprname = $2 + AND CASE o.oprkind WHEN 'l' THEN $1 IS NULL + ELSE _cmp_types(o.oprleft, _typename($1)) END + AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL + ELSE _cmp_types(o.oprright, _typename($3)) END + ); +$$ LANGUAGE SQL; + +-- has_operator( left_type, schema, name, right_type, return_type, description ) +CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _op_exists($1, $2, $3, $4, $5 ), $6 ); +$$ LANGUAGE SQL; + +-- has_operator( left_type, schema, name, right_type, return_type ) +CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _op_exists($1, $2, $3, $4, $5 ), + 'Operator ' || quote_ident($2) || '.' || $3 || '(' || $1 || ',' || $4 + || ') RETURNS ' || $5 || ' should exist' + ); +$$ LANGUAGE SQL; + +-- has_operator( left_type, name, right_type, return_type, description ) +CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _op_exists($1, $2, $3, $4 ), $5 ); +$$ LANGUAGE SQL; + +-- has_operator( left_type, name, right_type, return_type ) +CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _op_exists($1, $2, $3, $4 ), + 'Operator ' || $2 || '(' || $1 || ',' || $3 + || ') RETURNS ' || $4 || ' should exist' + ); +$$ LANGUAGE SQL; + +-- has_operator( left_type, name, right_type, description ) +CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _op_exists($1, $2, $3 ), $4 ); +$$ LANGUAGE SQL; + +-- has_operator( left_type, name, right_type ) +CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _op_exists($1, $2, $3 ), + 'Operator ' || $2 || '(' || $1 || ',' || $3 + || ') should exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_operator( left_type, schema, name, right_type, return_type, description ) +CREATE OR REPLACE FUNCTION hasnt_operator ( NAME, NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _op_exists($1, $2, $3, $4, $5 ), $6 ); +$$ LANGUAGE SQL; + +-- hasnt_operator( left_type, schema, name, right_type, return_type ) +CREATE OR REPLACE FUNCTION hasnt_operator ( NAME, NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _op_exists($1, $2, $3, $4, $5 ), + 'Operator ' || quote_ident($2) || '.' || $3 || '(' || $1 || ',' || $4 + || ') RETURNS ' || $5 || ' should not exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_operator( left_type, name, right_type, return_type, description ) +CREATE OR REPLACE FUNCTION hasnt_operator ( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _op_exists($1, $2, $3, $4 ), $5 ); +$$ LANGUAGE SQL; + +-- hasnt_operator( left_type, name, right_type, return_type ) +CREATE OR REPLACE FUNCTION hasnt_operator ( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _op_exists($1, $2, $3, $4 ), + 'Operator ' || $2 || '(' || $1 || ',' || $3 + || ') RETURNS ' || $4 || ' should not exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_operator( left_type, name, right_type, description ) +CREATE OR REPLACE FUNCTION hasnt_operator ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _op_exists($1, $2, $3 ), $4 ); +$$ LANGUAGE SQL; + +-- hasnt_operator( left_type, name, right_type ) +CREATE OR REPLACE FUNCTION hasnt_operator ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _op_exists($1, $2, $3 ), + 'Operator ' || $2 || '(' || $1 || ',' || $3 + || ') should not exist' + ); +$$ LANGUAGE SQL; + +-- has_leftop( schema, name, right_type, return_type, description ) +CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _op_exists(NULL, $1, $2, $3, $4), $5 ); +$$ LANGUAGE SQL; + +-- has_leftop( schema, name, right_type, return_type ) +CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _op_exists(NULL, $1, $2, $3, $4 ), + 'Left operator ' || quote_ident($1) || '.' || $2 || '(NONE,' + || $3 || ') RETURNS ' || $4 || ' should exist' + ); +$$ LANGUAGE SQL; + +-- has_leftop( name, right_type, return_type, description ) +CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _op_exists(NULL, $1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- has_leftop( name, right_type, return_type ) +CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _op_exists(NULL, $1, $2, $3 ), + 'Left operator ' || $1 || '(NONE,' || $2 || ') RETURNS ' || $3 || ' should exist' + ); +$$ LANGUAGE SQL; + +-- has_leftop( name, right_type, description ) +CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _op_exists(NULL, $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- has_leftop( name, right_type ) +CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _op_exists(NULL, $1, $2 ), + 'Left operator ' || $1 || '(NONE,' || $2 || ') should exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_leftop( schema, name, right_type, return_type, description ) +CREATE OR REPLACE FUNCTION hasnt_leftop ( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _op_exists(NULL, $1, $2, $3, $4), $5 ); +$$ LANGUAGE SQL; + +-- hasnt_leftop( schema, name, right_type, return_type ) +CREATE OR REPLACE FUNCTION hasnt_leftop ( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _op_exists(NULL, $1, $2, $3, $4 ), + 'Left operator ' || quote_ident($1) || '.' || $2 || '(NONE,' + || $3 || ') RETURNS ' || $4 || ' should not exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_leftop( name, right_type, return_type, description ) +CREATE OR REPLACE FUNCTION hasnt_leftop ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _op_exists(NULL, $1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- hasnt_leftop( name, right_type, return_type ) +CREATE OR REPLACE FUNCTION hasnt_leftop ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _op_exists(NULL, $1, $2, $3 ), + 'Left operator ' || $1 || '(NONE,' || $2 || ') RETURNS ' || $3 || ' should not exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_leftop( name, right_type, description ) +CREATE OR REPLACE FUNCTION hasnt_leftop ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _op_exists(NULL, $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_leftop( name, right_type ) +CREATE OR REPLACE FUNCTION hasnt_leftop ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _op_exists(NULL, $1, $2 ), + 'Left operator ' || $1 || '(NONE,' || $2 || ') should not exist' + ); +$$ LANGUAGE SQL; + +-- has_rightop( left_type, schema, name, return_type, description ) +CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _op_exists( $1, $2, $3, NULL, $4), $5 ); +$$ LANGUAGE SQL; + +-- has_rightop( left_type, schema, name, return_type ) +CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _op_exists($1, $2, $3, NULL, $4 ), + 'Right operator ' || quote_ident($2) || '.' || $3 || '(' + || $1 || ',NONE) RETURNS ' || $4 || ' should exist' + ); +$$ LANGUAGE SQL; + +-- has_rightop( left_type, name, return_type, description ) +CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _op_exists( $1, $2, NULL, $3), $4 ); +$$ LANGUAGE SQL; + +-- has_rightop( left_type, name, return_type ) +CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _op_exists($1, $2, NULL, $3 ), + 'Right operator ' || $2 || '(' + || $1 || ',NONE) RETURNS ' || $3 || ' should exist' + ); +$$ LANGUAGE SQL; + +-- has_rightop( left_type, name, description ) +CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _op_exists( $1, $2, NULL), $3 ); +$$ LANGUAGE SQL; + +-- has_rightop( left_type, name ) +CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _op_exists($1, $2, NULL ), + 'Right operator ' || $2 || '(' || $1 || ',NONE) should exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_rightop( left_type, schema, name, return_type, description ) +CREATE OR REPLACE FUNCTION hasnt_rightop ( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _op_exists( $1, $2, $3, NULL, $4), $5 ); +$$ LANGUAGE SQL; + +-- hasnt_rightop( left_type, schema, name, return_type ) +CREATE OR REPLACE FUNCTION hasnt_rightop ( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _op_exists($1, $2, $3, NULL, $4 ), + 'Right operator ' || quote_ident($2) || '.' || $3 || '(' + || $1 || ',NONE) RETURNS ' || $4 || ' should not exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_rightop( left_type, name, return_type, description ) +CREATE OR REPLACE FUNCTION hasnt_rightop ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _op_exists( $1, $2, NULL, $3), $4 ); +$$ LANGUAGE SQL; + +-- hasnt_rightop( left_type, name, return_type ) +CREATE OR REPLACE FUNCTION hasnt_rightop ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _op_exists($1, $2, NULL, $3 ), + 'Right operator ' || $2 || '(' + || $1 || ',NONE) RETURNS ' || $3 || ' should not exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_rightop( left_type, name, description ) +CREATE OR REPLACE FUNCTION hasnt_rightop ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _op_exists( $1, $2, NULL), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_rightop( left_type, name ) +CREATE OR REPLACE FUNCTION hasnt_rightop ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _op_exists($1, $2, NULL ), + 'Right operator ' || $2 || '(' || $1 || ',NONE) should not exist' + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _are ( text, name[], name[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + what ALIAS FOR $1; + extras ALIAS FOR $2; + missing ALIAS FOR $3; + descr ALIAS FOR $4; + msg TEXT := ''; + res BOOLEAN := TRUE; +BEGIN + IF extras[1] IS NOT NULL THEN + res = FALSE; + msg := E'\n' || diag( + ' Extra ' || what || E':\n ' + || _ident_array_to_sorted_string( extras, E'\n ' ) + ); + END IF; + IF missing[1] IS NOT NULL THEN + res = FALSE; + msg := msg || E'\n' || diag( + ' Missing ' || what || E':\n ' + || _ident_array_to_sorted_string( missing, E'\n ' ) + ); + END IF; + + RETURN ok(res, descr) || msg; +END; +$$ LANGUAGE plpgsql; + +-- tablespaces_are( tablespaces, description ) +CREATE OR REPLACE FUNCTION tablespaces_are ( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'tablespaces', + ARRAY( + SELECT spcname + FROM pg_catalog.pg_tablespace + EXCEPT + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + ), + ARRAY( + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + EXCEPT + SELECT spcname + FROM pg_catalog.pg_tablespace + ), + $2 + ); +$$ LANGUAGE SQL; + +-- tablespaces_are( tablespaces ) +CREATE OR REPLACE FUNCTION tablespaces_are ( NAME[] ) +RETURNS TEXT AS $$ + SELECT tablespaces_are( $1, 'There should be the correct tablespaces' ); +$$ LANGUAGE SQL; + +-- schemas_are( schemas, description ) +CREATE OR REPLACE FUNCTION schemas_are ( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'schemas', + ARRAY( + SELECT nspname + FROM pg_catalog.pg_namespace + WHERE nspname NOT LIKE 'pg\_%' + AND nspname <> 'information_schema' + EXCEPT + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + ), + ARRAY( + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + EXCEPT + SELECT nspname + FROM pg_catalog.pg_namespace + WHERE nspname NOT LIKE 'pg\_%' + AND nspname <> 'information_schema' + ), + $2 + ); +$$ LANGUAGE SQL; + +-- schemas_are( schemas ) +CREATE OR REPLACE FUNCTION schemas_are ( NAME[] ) +RETURNS TEXT AS $$ + SELECT schemas_are( $1, 'There should be the correct schemas' ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _extras ( CHAR[], NAME, NAME[] ) +RETURNS NAME[] AS $$ + SELECT ARRAY( + SELECT c.relname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + WHERE c.relkind = ANY($1) + AND n.nspname = $2 + AND c.relname NOT IN('pg_all_foreign_keys', 'tap_funky', '__tresults___numb_seq', '__tcache___id_seq') + EXCEPT + SELECT $3[i] + FROM generate_series(1, array_upper($3, 1)) s(i) + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _extras ( CHAR[], NAME[] ) +RETURNS NAME[] AS $$ + SELECT ARRAY( + SELECT c.relname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + WHERE pg_catalog.pg_table_is_visible(c.oid) + AND n.nspname <> 'pg_catalog' + AND c.relkind = ANY($1) + AND c.relname NOT IN ('__tcache__', 'pg_all_foreign_keys', 'tap_funky', '__tresults___numb_seq', '__tcache___id_seq') + EXCEPT + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _extras ( CHAR, NAME, NAME[] ) +RETURNS NAME[] AS $$ + SELECT _extras(ARRAY[$1], $2, $3); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _extras ( CHAR, NAME[] ) +RETURNS NAME[] AS $$ +SELECT _extras(ARRAY[$1], $2); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _missing ( CHAR[], NAME, NAME[] ) +RETURNS NAME[] AS $$ + SELECT ARRAY( + SELECT $3[i] + FROM generate_series(1, array_upper($3, 1)) s(i) + EXCEPT + SELECT c.relname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + WHERE c.relkind = ANY($1) + AND n.nspname = $2 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _missing ( CHAR[], NAME[] ) +RETURNS NAME[] AS $$ + SELECT ARRAY( + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + EXCEPT + SELECT c.relname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + WHERE pg_catalog.pg_table_is_visible(c.oid) + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + AND c.relkind = ANY($1) + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _missing ( CHAR, NAME, NAME[] ) +RETURNS NAME[] AS $$ + SELECT _missing(ARRAY[$1], $2, $3); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _missing ( CHAR, NAME[] ) +RETURNS NAME[] AS $$ + SELECT _missing(ARRAY[$1], $2); +$$ LANGUAGE SQL; + +-- tables_are( schema, tables, description ) +CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( 'tables', _extras('{r,p}'::char[], $1, $2), _missing('{r,p}'::char[], $1, $2), $3); +$$ LANGUAGE SQL; + +-- tables_are( tables, description ) +CREATE OR REPLACE FUNCTION tables_are ( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( 'tables', _extras('{r,p}'::char[], $1), _missing('{r,p}'::char[], $1), $2); +$$ LANGUAGE SQL; + +-- tables_are( schema, tables ) +CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _are( + 'tables', _extras('{r,p}'::char[], $1, $2), _missing('{r,p}'::char[], $1, $2), + 'Schema ' || quote_ident($1) || ' should have the correct tables' + ); +$$ LANGUAGE SQL; + +-- tables_are( tables ) +CREATE OR REPLACE FUNCTION tables_are ( NAME[] ) +RETURNS TEXT AS $$ + SELECT _are( + 'tables', _extras('{r,p}'::char[], $1), _missing('{r,p}'::char[], $1), + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct tables' + ); +$$ LANGUAGE SQL; + +-- views_are( schema, views, description ) +CREATE OR REPLACE FUNCTION views_are ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( 'views', _extras('v', $1, $2), _missing('v', $1, $2), $3); +$$ LANGUAGE SQL; + +-- views_are( views, description ) +CREATE OR REPLACE FUNCTION views_are ( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( 'views', _extras('v', $1), _missing('v', $1), $2); +$$ LANGUAGE SQL; + +-- views_are( schema, views ) +CREATE OR REPLACE FUNCTION views_are ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _are( + 'views', _extras('v', $1, $2), _missing('v', $1, $2), + 'Schema ' || quote_ident($1) || ' should have the correct views' + ); +$$ LANGUAGE SQL; + +-- views_are( views ) +CREATE OR REPLACE FUNCTION views_are ( NAME[] ) +RETURNS TEXT AS $$ + SELECT _are( + 'views', _extras('v', $1), _missing('v', $1), + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views' + ); +$$ LANGUAGE SQL; + +-- sequences_are( schema, sequences, description ) +CREATE OR REPLACE FUNCTION sequences_are ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( 'sequences', _extras('S', $1, $2), _missing('S', $1, $2), $3); +$$ LANGUAGE SQL; + +-- sequences_are( sequences, description ) +CREATE OR REPLACE FUNCTION sequences_are ( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( 'sequences', _extras('S', $1), _missing('S', $1), $2); +$$ LANGUAGE SQL; + +-- sequences_are( schema, sequences ) +CREATE OR REPLACE FUNCTION sequences_are ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _are( + 'sequences', _extras('S', $1, $2), _missing('S', $1, $2), + 'Schema ' || quote_ident($1) || ' should have the correct sequences' + ); +$$ LANGUAGE SQL; + +-- sequences_are( sequences ) +CREATE OR REPLACE FUNCTION sequences_are ( NAME[] ) +RETURNS TEXT AS $$ + SELECT _are( + 'sequences', _extras('S', $1), _missing('S', $1), + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct sequences' + ); +$$ LANGUAGE SQL; + +-- functions_are( schema, functions[], description ) +CREATE OR REPLACE FUNCTION functions_are ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'functions', + ARRAY( + SELECT name FROM tap_funky WHERE schema = $1 + EXCEPT + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + ), + ARRAY( + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + EXCEPT + SELECT name FROM tap_funky WHERE schema = $1 + ), + $3 + ); +$$ LANGUAGE SQL; + +-- functions_are( schema, functions[] ) +CREATE OR REPLACE FUNCTION functions_are ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT functions_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct functions' ); +$$ LANGUAGE SQL; + +-- functions_are( functions[], description ) +CREATE OR REPLACE FUNCTION functions_are ( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'functions', + ARRAY( + SELECT name FROM tap_funky WHERE is_visible + AND schema NOT IN ('pg_catalog', 'information_schema') + EXCEPT + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + ), + ARRAY( + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + EXCEPT + SELECT name FROM tap_funky WHERE is_visible + AND schema NOT IN ('pg_catalog', 'information_schema') + ), + $2 + ); +$$ LANGUAGE SQL; + +-- functions_are( functions[] ) +CREATE OR REPLACE FUNCTION functions_are ( NAME[] ) +RETURNS TEXT AS $$ + SELECT functions_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct functions' ); +$$ LANGUAGE SQL; + +-- indexes_are( schema, table, indexes[], description ) +CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'indexes', + ARRAY( + SELECT ci.relname + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace + WHERE ct.relname = $2 + AND n.nspname = $1 + EXCEPT + SELECT $3[i] + FROM generate_series(1, array_upper($3, 1)) s(i) + ), + ARRAY( + SELECT $3[i] + FROM generate_series(1, array_upper($3, 1)) s(i) + EXCEPT + SELECT ci.relname + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace + WHERE ct.relname = $2 + AND n.nspname = $1 + ), + $4 + ); +$$ LANGUAGE SQL; + +-- indexes_are( schema, table, indexes[] ) +CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT indexes_are( $1, $2, $3, 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct indexes' ); +$$ LANGUAGE SQL; + +-- indexes_are( table, indexes[], description ) +CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'indexes', + ARRAY( + SELECT ci.relname + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace + WHERE ct.relname = $1 + AND pg_catalog.pg_table_is_visible(ct.oid) + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + EXCEPT + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + ), + ARRAY( + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + EXCEPT + SELECT ci.relname + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace + WHERE ct.relname = $1 + AND pg_catalog.pg_table_is_visible(ct.oid) + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + ), + $3 + ); +$$ LANGUAGE SQL; + +-- indexes_are( table, indexes[] ) +CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT indexes_are( $1, $2, 'Table ' || quote_ident($1) || ' should have the correct indexes' ); +$$ LANGUAGE SQL; + +-- users_are( users[], description ) +CREATE OR REPLACE FUNCTION users_are( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'users', + ARRAY( + SELECT usename + FROM pg_catalog.pg_user + EXCEPT + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + ), + ARRAY( + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + EXCEPT + SELECT usename + FROM pg_catalog.pg_user + ), + $2 + ); +$$ LANGUAGE SQL; + +-- users_are( users[] ) +CREATE OR REPLACE FUNCTION users_are( NAME[] ) +RETURNS TEXT AS $$ + SELECT users_are( $1, 'There should be the correct users' ); +$$ LANGUAGE SQL; + +-- groups_are( groups[], description ) +CREATE OR REPLACE FUNCTION groups_are( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'groups', + ARRAY( + SELECT groname + FROM pg_catalog.pg_group + EXCEPT + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + ), + ARRAY( + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + EXCEPT + SELECT groname + FROM pg_catalog.pg_group + ), + $2 + ); +$$ LANGUAGE SQL; + +-- groups_are( groups[] ) +CREATE OR REPLACE FUNCTION groups_are( NAME[] ) +RETURNS TEXT AS $$ + SELECT groups_are( $1, 'There should be the correct groups' ); +$$ LANGUAGE SQL; + +-- languages_are( languages[], description ) +CREATE OR REPLACE FUNCTION languages_are( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'languages', + ARRAY( + SELECT lanname + FROM pg_catalog.pg_language + WHERE lanispl + EXCEPT + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + ), + ARRAY( + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + EXCEPT + SELECT lanname + FROM pg_catalog.pg_language + WHERE lanispl + ), + $2 + ); +$$ LANGUAGE SQL; + +-- languages_are( languages[] ) +CREATE OR REPLACE FUNCTION languages_are( NAME[] ) +RETURNS TEXT AS $$ + SELECT languages_are( $1, 'There should be the correct procedural languages' ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _is_trusted( NAME ) +RETURNS BOOLEAN AS $$ + SELECT lanpltrusted FROM pg_catalog.pg_language WHERE lanname = $1; +$$ LANGUAGE SQL; + +-- has_language( language, description) +CREATE OR REPLACE FUNCTION has_language( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _is_trusted($1) IS NOT NULL, $2 ); +$$ LANGUAGE SQL; + +-- has_language( language ) +CREATE OR REPLACE FUNCTION has_language( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _is_trusted($1) IS NOT NULL, 'Procedural language ' || quote_ident($1) || ' should exist' ); +$$ LANGUAGE SQL; + +-- hasnt_language( language, description) +CREATE OR REPLACE FUNCTION hasnt_language( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _is_trusted($1) IS NULL, $2 ); +$$ LANGUAGE SQL; + +-- hasnt_language( language ) +CREATE OR REPLACE FUNCTION hasnt_language( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _is_trusted($1) IS NULL, 'Procedural language ' || quote_ident($1) || ' should not exist' ); +$$ LANGUAGE SQL; + +-- language_is_trusted( language, description ) +CREATE OR REPLACE FUNCTION language_is_trusted( NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + is_trusted boolean := _is_trusted($1); +BEGIN + IF is_trusted IS NULL THEN + RETURN fail( $2 ) || E'\n' || diag( ' Procedural language ' || quote_ident($1) || ' does not exist') ; + END IF; + RETURN ok( is_trusted, $2 ); +END; +$$ LANGUAGE plpgsql; + +-- language_is_trusted( language ) +CREATE OR REPLACE FUNCTION language_is_trusted( NAME ) +RETURNS TEXT AS $$ + SELECT language_is_trusted($1, 'Procedural language ' || quote_ident($1) || ' should be trusted' ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _opc_exists( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS ( + SELECT TRUE + FROM pg_catalog.pg_opclass oc + JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid + WHERE n.nspname = $1 + AND oc.opcname = $2 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _opc_exists( NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS ( + SELECT TRUE + FROM pg_catalog.pg_opclass oc + WHERE oc.opcname = $1 + AND pg_opclass_is_visible(oid) + ); +$$ LANGUAGE SQL; + +-- has_opclass( schema, name, description ) +CREATE OR REPLACE FUNCTION has_opclass( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _opc_exists( $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- has_opclass( schema, name ) +CREATE OR REPLACE FUNCTION has_opclass( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( _opc_exists( $1, $2 ), 'Operator class ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); +$$ LANGUAGE SQL; + +-- has_opclass( name, description ) +CREATE OR REPLACE FUNCTION has_opclass( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _opc_exists( $1 ), $2) +$$ LANGUAGE SQL; + +-- has_opclass( name ) +CREATE OR REPLACE FUNCTION has_opclass( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _opc_exists( $1 ), 'Operator class ' || quote_ident($1) || ' should exist' ); +$$ LANGUAGE SQL; + +-- hasnt_opclass( schema, name, description ) +CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _opc_exists( $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_opclass( schema, name ) +CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _opc_exists( $1, $2 ), 'Operator class ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); +$$ LANGUAGE SQL; + +-- hasnt_opclass( name, description ) +CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _opc_exists( $1 ), $2) +$$ LANGUAGE SQL; + +-- hasnt_opclass( name ) +CREATE OR REPLACE FUNCTION hasnt_opclass( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _opc_exists( $1 ), 'Operator class ' || quote_ident($1) || ' should not exist' ); +$$ LANGUAGE SQL; + +-- opclasses_are( schema, opclasses[], description ) +CREATE OR REPLACE FUNCTION opclasses_are ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'operator classes', + ARRAY( + SELECT oc.opcname + FROM pg_catalog.pg_opclass oc + JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid + WHERE n.nspname = $1 + EXCEPT + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + ), + ARRAY( + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + EXCEPT + SELECT oc.opcname + FROM pg_catalog.pg_opclass oc + JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid + WHERE n.nspname = $1 + ), + $3 + ); +$$ LANGUAGE SQL; + +-- opclasses_are( schema, opclasses[] ) +CREATE OR REPLACE FUNCTION opclasses_are ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT opclasses_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct operator classes' ); +$$ LANGUAGE SQL; + +-- opclasses_are( opclasses[], description ) +CREATE OR REPLACE FUNCTION opclasses_are ( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'operator classes', + ARRAY( + SELECT oc.opcname + FROM pg_catalog.pg_opclass oc + JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + AND pg_catalog.pg_opclass_is_visible(oc.oid) + EXCEPT + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + ), + ARRAY( + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + EXCEPT + SELECT oc.opcname + FROM pg_catalog.pg_opclass oc + JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + AND pg_catalog.pg_opclass_is_visible(oc.oid) + ), + $2 + ); +$$ LANGUAGE SQL; + +-- opclasses_are( opclasses[] ) +CREATE OR REPLACE FUNCTION opclasses_are ( NAME[] ) +RETURNS TEXT AS $$ + SELECT opclasses_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct operator classes' ); +$$ LANGUAGE SQL; + +-- rules_are( schema, table, rules[], description ) +CREATE OR REPLACE FUNCTION rules_are( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'rules', + ARRAY( + SELECT r.rulename + FROM pg_catalog.pg_rewrite r + JOIN pg_catalog.pg_class c ON c.oid = r.ev_class + JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid + WHERE c.relname = $2 + AND n.nspname = $1 + EXCEPT + SELECT $3[i] + FROM generate_series(1, array_upper($3, 1)) s(i) + ), + ARRAY( + SELECT $3[i] + FROM generate_series(1, array_upper($3, 1)) s(i) + EXCEPT + SELECT r.rulename + FROM pg_catalog.pg_rewrite r + JOIN pg_catalog.pg_class c ON c.oid = r.ev_class + JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid + WHERE c.relname = $2 + AND n.nspname = $1 + ), + $4 + ); +$$ LANGUAGE SQL; + +-- rules_are( schema, table, rules[] ) +CREATE OR REPLACE FUNCTION rules_are( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT rules_are( $1, $2, $3, 'Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct rules' ); +$$ LANGUAGE SQL; + +-- rules_are( table, rules[], description ) +CREATE OR REPLACE FUNCTION rules_are( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'rules', + ARRAY( + SELECT r.rulename + FROM pg_catalog.pg_rewrite r + JOIN pg_catalog.pg_class c ON c.oid = r.ev_class + JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid + WHERE c.relname = $1 + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + AND pg_catalog.pg_table_is_visible(c.oid) + EXCEPT + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + ), + ARRAY( + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + EXCEPT + SELECT r.rulename + FROM pg_catalog.pg_rewrite r + JOIN pg_catalog.pg_class c ON c.oid = r.ev_class + JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid + AND c.relname = $1 + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + AND pg_catalog.pg_table_is_visible(c.oid) + ), + $3 + ); +$$ LANGUAGE SQL; + +-- rules_are( table, rules[] ) +CREATE OR REPLACE FUNCTION rules_are( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT rules_are( $1, $2, 'Relation ' || quote_ident($1) || ' should have the correct rules' ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _is_instead( NAME, NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT r.is_instead + FROM pg_catalog.pg_rewrite r + JOIN pg_catalog.pg_class c ON c.oid = r.ev_class + JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid + WHERE r.rulename = $3 + AND c.relname = $2 + AND n.nspname = $1 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _is_instead( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT r.is_instead + FROM pg_catalog.pg_rewrite r + JOIN pg_catalog.pg_class c ON c.oid = r.ev_class + WHERE r.rulename = $2 + AND c.relname = $1 + AND pg_catalog.pg_table_is_visible(c.oid) +$$ LANGUAGE SQL; + +-- has_rule( schema, table, rule, description ) +CREATE OR REPLACE FUNCTION has_rule( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _is_instead($1, $2, $3) IS NOT NULL, $4 ); +$$ LANGUAGE SQL; + +-- has_rule( schema, table, rule ) +CREATE OR REPLACE FUNCTION has_rule( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( _is_instead($1, $2, $3) IS NOT NULL, 'Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should have rule ' || quote_ident($3) ); +$$ LANGUAGE SQL; + +-- has_rule( table, rule, description ) +CREATE OR REPLACE FUNCTION has_rule( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _is_instead($1, $2) IS NOT NULL, $3 ); +$$ LANGUAGE SQL; + +-- has_rule( table, rule ) +CREATE OR REPLACE FUNCTION has_rule( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( _is_instead($1, $2) IS NOT NULL, 'Relation ' || quote_ident($1) || ' should have rule ' || quote_ident($2) ); +$$ LANGUAGE SQL; + +-- hasnt_rule( schema, table, rule, description ) +CREATE OR REPLACE FUNCTION hasnt_rule( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _is_instead($1, $2, $3) IS NULL, $4 ); +$$ LANGUAGE SQL; + +-- hasnt_rule( schema, table, rule ) +CREATE OR REPLACE FUNCTION hasnt_rule( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( _is_instead($1, $2, $3) IS NULL, 'Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should not have rule ' || quote_ident($3) ); +$$ LANGUAGE SQL; + +-- hasnt_rule( table, rule, description ) +CREATE OR REPLACE FUNCTION hasnt_rule( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _is_instead($1, $2) IS NULL, $3 ); +$$ LANGUAGE SQL; + +-- hasnt_rule( table, rule ) +CREATE OR REPLACE FUNCTION hasnt_rule( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( _is_instead($1, $2) IS NULL, 'Relation ' || quote_ident($1) || ' should not have rule ' || quote_ident($2) ); +$$ LANGUAGE SQL; + +-- rule_is_instead( schema, table, rule, description ) +CREATE OR REPLACE FUNCTION rule_is_instead( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + is_it boolean := _is_instead($1, $2, $3); +BEGIN + IF is_it IS NOT NULL THEN RETURN ok( is_it, $4 ); END IF; + RETURN ok( FALSE, $4 ) || E'\n' || diag( + ' Rule ' || quote_ident($3) || ' does not exist' + ); +END; +$$ LANGUAGE plpgsql; + +-- rule_is_instead( schema, table, rule ) +CREATE OR REPLACE FUNCTION rule_is_instead( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT rule_is_instead( $1, $2, $3, 'Rule ' || quote_ident($3) || ' on relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should be an INSTEAD rule' ); +$$ LANGUAGE SQL; + +-- rule_is_instead( table, rule, description ) +CREATE OR REPLACE FUNCTION rule_is_instead( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + is_it boolean := _is_instead($1, $2); +BEGIN + IF is_it IS NOT NULL THEN RETURN ok( is_it, $3 ); END IF; + RETURN ok( FALSE, $3 ) || E'\n' || diag( + ' Rule ' || quote_ident($2) || ' does not exist' + ); +END; +$$ LANGUAGE plpgsql; + +-- rule_is_instead( table, rule ) +CREATE OR REPLACE FUNCTION rule_is_instead( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT rule_is_instead($1, $2, 'Rule ' || quote_ident($2) || ' on relation ' || quote_ident($1) || ' should be an INSTEAD rule' ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _expand_on( char ) +RETURNS text AS $$ + SELECT CASE $1 + WHEN '1' THEN 'SELECT' + WHEN '2' THEN 'UPDATE' + WHEN '3' THEN 'INSERT' + WHEN '4' THEN 'DELETE' + ELSE 'UNKNOWN' END +$$ LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE FUNCTION _contract_on( TEXT ) +RETURNS "char" AS $$ + SELECT CASE substring(LOWER($1) FROM 1 FOR 1) + WHEN 's' THEN '1'::"char" + WHEN 'u' THEN '2'::"char" + WHEN 'i' THEN '3'::"char" + WHEN 'd' THEN '4'::"char" + ELSE '0'::"char" END +$$ LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE FUNCTION _rule_on( NAME, NAME, NAME ) +RETURNS "char" AS $$ + SELECT r.ev_type + FROM pg_catalog.pg_rewrite r + JOIN pg_catalog.pg_class c ON c.oid = r.ev_class + JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid + WHERE r.rulename = $3 + AND c.relname = $2 + AND n.nspname = $1 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _rule_on( NAME, NAME ) +RETURNS "char" AS $$ + SELECT r.ev_type + FROM pg_catalog.pg_rewrite r + JOIN pg_catalog.pg_class c ON c.oid = r.ev_class + WHERE r.rulename = $2 + AND c.relname = $1 +$$ LANGUAGE SQL; + +-- rule_is_on( schema, table, rule, event, description ) +CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + want char := _contract_on($4); + have char := _rule_on($1, $2, $3); +BEGIN + IF have IS NOT NULL THEN + RETURN is( _expand_on(have), _expand_on(want), $5 ); + END IF; + + RETURN ok( false, $5 ) || E'\n' || diag( + ' Rule ' || quote_ident($3) || ' does not exist on ' + || quote_ident($1) || '.' || quote_ident($2) + ); +END; +$$ LANGUAGE plpgsql; + +-- rule_is_on( schema, table, rule, event ) +CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT rule_is_on( + $1, $2, $3, $4, + 'Rule ' || quote_ident($3) || ' should be on ' || _expand_on(_contract_on($4)::char) + || ' to ' || quote_ident($1) || '.' || quote_ident($2) + ); +$$ LANGUAGE SQL; + +-- rule_is_on( table, rule, event, description ) +CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + want char := _contract_on($3); + have char := _rule_on($1, $2); +BEGIN + IF have IS NOT NULL THEN + RETURN is( _expand_on(have), _expand_on(want), $4 ); + END IF; + + RETURN ok( false, $4 ) || E'\n' || diag( + ' Rule ' || quote_ident($2) || ' does not exist on ' + || quote_ident($1) + ); +END; +$$ LANGUAGE plpgsql; + +-- rule_is_on( table, rule, event ) +CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT rule_is_on( + $1, $2, $3, + 'Rule ' || quote_ident($2) || ' should be on ' + || _expand_on(_contract_on($3)::char) || ' to ' || quote_ident($1) + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _nosuch( NAME, NAME, NAME[]) +RETURNS TEXT AS $$ + SELECT E'\n' || diag( + ' Function ' + || CASE WHEN $1 IS NOT NULL THEN quote_ident($1) || '.' ELSE '' END + || quote_ident($2) || '(' + || array_to_string($3, ', ') || ') does not exist' + ); +$$ LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE FUNCTION _func_compare( NAME, NAME, NAME[], anyelement, anyelement, TEXT) +RETURNS TEXT AS $$ + SELECT CASE WHEN $4 IS NULL + THEN ok( FALSE, $6 ) || _nosuch($1, $2, $3) + ELSE is( $4, $5, $6 ) + END; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _func_compare( NAME, NAME, NAME[], boolean, TEXT) +RETURNS TEXT AS $$ + SELECT CASE WHEN $4 IS NULL + THEN ok( FALSE, $5 ) || _nosuch($1, $2, $3) + ELSE ok( $4, $5 ) + END; +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _func_compare( NAME, NAME, anyelement, anyelement, TEXT) +RETURNS TEXT AS $$ + SELECT CASE WHEN $3 IS NULL + THEN ok( FALSE, $5 ) || _nosuch($1, $2, '{}') + ELSE is( $3, $4, $5 ) + END; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _func_compare( NAME, NAME, boolean, TEXT) +RETURNS TEXT AS $$ + SELECT CASE WHEN $3 IS NULL + THEN ok( FALSE, $4 ) || _nosuch($1, $2, '{}') + ELSE ok( $3, $4 ) + END; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _lang ( NAME, NAME, NAME[] ) +RETURNS NAME AS $$ + SELECT l.lanname + FROM tap_funky f + JOIN pg_catalog.pg_language l ON f.langoid = l.oid + WHERE f.schema = $1 + and f.name = $2 + AND f.args = _funkargs($3) +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _lang ( NAME, NAME ) +RETURNS NAME AS $$ + SELECT l.lanname + FROM tap_funky f + JOIN pg_catalog.pg_language l ON f.langoid = l.oid + WHERE f.schema = $1 + and f.name = $2 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _lang ( NAME, NAME[] ) +RETURNS NAME AS $$ + SELECT l.lanname + FROM tap_funky f + JOIN pg_catalog.pg_language l ON f.langoid = l.oid + WHERE f.name = $1 + AND f.args = _funkargs($2) + AND f.is_visible; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _lang ( NAME ) +RETURNS NAME AS $$ + SELECT l.lanname + FROM tap_funky f + JOIN pg_catalog.pg_language l ON f.langoid = l.oid + WHERE f.name = $1 + AND f.is_visible; +$$ LANGUAGE SQL; + +-- function_lang_is( schema, function, args[], language, description ) +CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, NAME[], NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, $3, _lang($1, $2, $3), $4, $5 ); +$$ LANGUAGE SQL; + +-- function_lang_is( schema, function, args[], language ) +CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, NAME[], NAME ) +RETURNS TEXT AS $$ + SELECT function_lang_is( + $1, $2, $3, $4, + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should be written in ' || quote_ident($4) + ); +$$ LANGUAGE SQL; + +-- function_lang_is( schema, function, language, description ) +CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, _lang($1, $2), $3, $4 ); +$$ LANGUAGE SQL; + +-- function_lang_is( schema, function, language ) +CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT function_lang_is( + $1, $2, $3, + 'Function ' || quote_ident($1) || '.' || quote_ident($2) + || '() should be written in ' || quote_ident($3) + ); +$$ LANGUAGE SQL; + +-- function_lang_is( function, args[], language, description ) +CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME[], NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, $2, _lang($1, $2), $3, $4 ); +$$ LANGUAGE SQL; + +-- function_lang_is( function, args[], language ) +CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME[], NAME ) +RETURNS TEXT AS $$ + SELECT function_lang_is( + $1, $2, $3, + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should be written in ' || quote_ident($3) + ); +$$ LANGUAGE SQL; + +-- function_lang_is( function, language, description ) +CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, _lang($1), $2, $3 ); +$$ LANGUAGE SQL; + +-- function_lang_is( function, language ) +CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT function_lang_is( + $1, $2, + 'Function ' || quote_ident($1) + || '() should be written in ' || quote_ident($2) + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _returns ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT returns + FROM tap_funky + WHERE schema = $1 + AND name = $2 + AND args = _funkargs($3) +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _returns ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT returns FROM tap_funky WHERE schema = $1 AND name = $2 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _returns ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT returns + FROM tap_funky + WHERE name = $1 + AND args = _funkargs($2) + AND is_visible; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _returns ( NAME ) +RETURNS TEXT AS $$ + SELECT returns FROM tap_funky WHERE name = $1 AND is_visible; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _retval(TEXT) +RETURNS TEXT AS $$ +DECLARE + setof TEXT := substring($1 FROM '^setof[[:space:]]+'); +BEGIN + IF setof IS NULL THEN RETURN _typename($1); END IF; + RETURN setof || _typename(substring($1 FROM char_length(setof)+1)); +END; +$$ LANGUAGE plpgsql; + +-- function_returns( schema, function, args[], type, description ) +CREATE OR REPLACE FUNCTION function_returns( NAME, NAME, NAME[], TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, $3, _returns($1, $2, $3), _retval($4), $5 ); +$$ LANGUAGE SQL; + +-- function_returns( schema, function, args[], type ) +CREATE OR REPLACE FUNCTION function_returns( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT function_returns( + $1, $2, $3, $4, + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should return ' || $4 + ); +$$ LANGUAGE SQL; + +-- function_returns( schema, function, type, description ) +CREATE OR REPLACE FUNCTION function_returns( NAME, NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, _returns($1, $2), _retval($3), $4 ); +$$ LANGUAGE SQL; + +-- function_returns( schema, function, type ) +CREATE OR REPLACE FUNCTION function_returns( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT function_returns( + $1, $2, $3, + 'Function ' || quote_ident($1) || '.' || quote_ident($2) + || '() should return ' || $3 + ); +$$ LANGUAGE SQL; + +-- function_returns( function, args[], type, description ) +CREATE OR REPLACE FUNCTION function_returns( NAME, NAME[], TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, $2, _returns($1, $2), _retval($3), $4 ); +$$ LANGUAGE SQL; + +-- function_returns( function, args[], type ) +CREATE OR REPLACE FUNCTION function_returns( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT function_returns( + $1, $2, $3, + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should return ' || $3 + ); +$$ LANGUAGE SQL; + +-- function_returns( function, type, description ) +CREATE OR REPLACE FUNCTION function_returns( NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, _returns($1), _retval($2), $3 ); +$$ LANGUAGE SQL; + +-- function_returns( function, type ) +CREATE OR REPLACE FUNCTION function_returns( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT function_returns( + $1, $2, + 'Function ' || quote_ident($1) || '() should return ' || $2 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _definer ( NAME, NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT is_definer + FROM tap_funky + WHERE schema = $1 + AND name = $2 + AND args = _funkargs($3) +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _definer ( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT is_definer FROM tap_funky WHERE schema = $1 AND name = $2 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _definer ( NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT is_definer + FROM tap_funky + WHERE name = $1 + AND args = _funkargs($2) + AND is_visible; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _definer ( NAME ) +RETURNS BOOLEAN AS $$ + SELECT is_definer FROM tap_funky WHERE name = $1 AND is_visible; +$$ LANGUAGE SQL; + +-- is_definer( schema, function, args[], description ) +CREATE OR REPLACE FUNCTION is_definer ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, $3, _definer($1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- is_definer( schema, function, args[] ) +CREATE OR REPLACE FUNCTION is_definer( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + _definer($1, $2, $3), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should be security definer' + ); +$$ LANGUAGE sql; + +-- is_definer( schema, function, description ) +CREATE OR REPLACE FUNCTION is_definer ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, _definer($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- is_definer( schema, function ) +CREATE OR REPLACE FUNCTION is_definer( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _definer($1, $2), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should be security definer' + ); +$$ LANGUAGE sql; + +-- is_definer( function, args[], description ) +CREATE OR REPLACE FUNCTION is_definer ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, $2, _definer($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- is_definer( function, args[] ) +CREATE OR REPLACE FUNCTION is_definer( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + _definer($1, $2), + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should be security definer' + ); +$$ LANGUAGE sql; + +-- is_definer( function, description ) +CREATE OR REPLACE FUNCTION is_definer( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, _definer($1), $2 ); +$$ LANGUAGE sql; + +-- is_definer( function ) +CREATE OR REPLACE FUNCTION is_definer( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _definer($1), 'Function ' || quote_ident($1) || '() should be security definer' ); +$$ LANGUAGE sql; + +-- isnt_definer( schema, function, args[], description ) +CREATE OR REPLACE FUNCTION isnt_definer ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, $3, NOT _definer($1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- isnt_definer( schema, function, args[] ) +CREATE OR REPLACE FUNCTION isnt_definer( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _definer($1, $2, $3), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should not be security definer' + ); +$$ LANGUAGE sql; + +-- isnt_definer( schema, function, description ) +CREATE OR REPLACE FUNCTION isnt_definer ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, NOT _definer($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_definer( schema, function ) +CREATE OR REPLACE FUNCTION isnt_definer( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _definer($1, $2), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not be security definer' + ); +$$ LANGUAGE sql; + +-- isnt_definer( function, args[], description ) +CREATE OR REPLACE FUNCTION isnt_definer ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, $2, NOT _definer($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_definer( function, args[] ) +CREATE OR REPLACE FUNCTION isnt_definer( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _definer($1, $2), + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should not be security definer' + ); +$$ LANGUAGE sql; + +-- isnt_definer( function, description ) +CREATE OR REPLACE FUNCTION isnt_definer( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, NOT _definer($1), $2 ); +$$ LANGUAGE sql; + +-- isnt_definer( function ) +CREATE OR REPLACE FUNCTION isnt_definer( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _definer($1), 'Function ' || quote_ident($1) || '() should not be security definer' ); +$$ LANGUAGE sql; + +-- Returns true if the specified function exists and is the specified type, +-- false if it exists and is not the specified type, and NULL if it does not +-- exist. Types are f for a normal function, p for a procedure, a for an +-- aggregate function, or w for a window function +-- _type_func(type, schema, function, args[]) +CREATE OR REPLACE FUNCTION _type_func ( "char", NAME, NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT kind = $1 + FROM tap_funky + WHERE schema = $2 + AND name = $3 + AND args = _funkargs($4) +$$ LANGUAGE SQL; + +-- _type_func(type, schema, function) +CREATE OR REPLACE FUNCTION _type_func ( "char", NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT kind = $1 FROM tap_funky WHERE schema = $2 AND name = $3 +$$ LANGUAGE SQL; + +-- _type_func(type, function, args[]) +CREATE OR REPLACE FUNCTION _type_func ( "char", NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT kind = $1 + FROM tap_funky + WHERE name = $2 + AND args = _funkargs($3) + AND is_visible; +$$ LANGUAGE SQL; + +-- _type_func(type, function) +CREATE OR REPLACE FUNCTION _type_func ( "char", NAME ) +RETURNS BOOLEAN AS $$ + SELECT kind = $1 FROM tap_funky WHERE name = $2 AND is_visible; +$$ LANGUAGE SQL; + +-- is_aggregate( schema, function, args[], description ) +CREATE OR REPLACE FUNCTION is_aggregate ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, $3, _type_func( 'a', $1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- is_aggregate( schema, function, args[] ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _func_compare( + $1, $2, $3, _type_func('a', $1, $2, $3), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should be an aggregate function' + ); +$$ LANGUAGE sql; + +-- is_aggregate( schema, function, description ) +CREATE OR REPLACE FUNCTION is_aggregate ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, _type_func('a', $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- is_aggregate( schema, function ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT _func_compare( + $1, $2, _type_func('a', $1, $2), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should be an aggregate function' + ); +$$ LANGUAGE sql; + +-- is_aggregate( function, args[], description ) +CREATE OR REPLACE FUNCTION is_aggregate ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare( NULL, $1, $2, _type_func('a', $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- is_aggregate( function, args[] ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _func_compare( + NULL, $1, $2, _type_func('a', $1, $2), + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should be an aggregate function' + ); +$$ LANGUAGE sql; + +-- is_aggregate( function, description ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, _type_func('a', $1), $2 ); +$$ LANGUAGE sql; + +-- is_aggregate( function ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME ) +RETURNS TEXT AS $$ + SELECT _func_compare( + NULL, $1, _type_func('a', $1), + 'Function ' || quote_ident($1) || '() should be an aggregate function' + ); +$$ LANGUAGE sql; + +-- isnt_aggregate( schema, function, args[], description ) +CREATE OR REPLACE FUNCTION isnt_aggregate ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, $3, NOT _type_func('a', $1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- isnt_aggregate( schema, function, args[] ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _func_compare( + $1, $2, $3, NOT _type_func('a', $1, $2, $3), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should not be an aggregate function' + ); +$$ LANGUAGE sql; + +-- isnt_aggregate( schema, function, description ) +CREATE OR REPLACE FUNCTION isnt_aggregate ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, NOT _type_func('a', $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_aggregate( schema, function ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT _func_compare( + $1, $2, NOT _type_func('a', $1, $2), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not be an aggregate function' + ); +$$ LANGUAGE sql; + +-- isnt_aggregate( function, args[], description ) +CREATE OR REPLACE FUNCTION isnt_aggregate ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, $2, NOT _type_func('a', $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_aggregate( function, args[] ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _func_compare( + NULL, $1, $2, NOT _type_func('a', $1, $2), + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should not be an aggregate function' + ); +$$ LANGUAGE sql; + +-- isnt_aggregate( function, description ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, NOT _type_func('a', $1), $2 ); +$$ LANGUAGE sql; + +-- isnt_aggregate( function ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME ) +RETURNS TEXT AS $$ + SELECT _func_compare( + NULL, $1, NOT _type_func('a', $1), + 'Function ' || quote_ident($1) || '() should not be an aggregate function' + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _strict ( NAME, NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT is_strict + FROM tap_funky + WHERE schema = $1 + AND name = $2 + AND args = _funkargs($3) +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _strict ( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT is_strict FROM tap_funky WHERE schema = $1 AND name = $2 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _strict ( NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT is_strict + FROM tap_funky + WHERE name = $1 + AND args = _funkargs($2) + AND is_visible; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _strict ( NAME ) +RETURNS BOOLEAN AS $$ + SELECT is_strict FROM tap_funky WHERE name = $1 AND is_visible; +$$ LANGUAGE SQL; + +-- is_strict( schema, function, args[], description ) +CREATE OR REPLACE FUNCTION is_strict ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, $3, _strict($1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- is_strict( schema, function, args[] ) +CREATE OR REPLACE FUNCTION is_strict( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + _strict($1, $2, $3), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should be strict' + ); +$$ LANGUAGE sql; + +-- is_strict( schema, function, description ) +CREATE OR REPLACE FUNCTION is_strict ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, _strict($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- is_strict( schema, function ) +CREATE OR REPLACE FUNCTION is_strict( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _strict($1, $2), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should be strict' + ); +$$ LANGUAGE sql; + +-- is_strict( function, args[], description ) +CREATE OR REPLACE FUNCTION is_strict ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, $2, _strict($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- is_strict( function, args[] ) +CREATE OR REPLACE FUNCTION is_strict( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + _strict($1, $2), + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should be strict' + ); +$$ LANGUAGE sql; + +-- is_strict( function, description ) +CREATE OR REPLACE FUNCTION is_strict( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, _strict($1), $2 ); +$$ LANGUAGE sql; + +-- is_strict( function ) +CREATE OR REPLACE FUNCTION is_strict( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _strict($1), 'Function ' || quote_ident($1) || '() should be strict' ); +$$ LANGUAGE sql; + +-- isnt_strict( schema, function, args[], description ) +CREATE OR REPLACE FUNCTION isnt_strict ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, $3, NOT _strict($1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- isnt_strict( schema, function, args[] ) +CREATE OR REPLACE FUNCTION isnt_strict( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _strict($1, $2, $3), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should not be strict' + ); +$$ LANGUAGE sql; + +-- isnt_strict( schema, function, description ) +CREATE OR REPLACE FUNCTION isnt_strict ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, NOT _strict($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_strict( schema, function ) +CREATE OR REPLACE FUNCTION isnt_strict( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _strict($1, $2), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not be strict' + ); +$$ LANGUAGE sql; + +-- isnt_strict( function, args[], description ) +CREATE OR REPLACE FUNCTION isnt_strict ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, $2, NOT _strict($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_strict( function, args[] ) +CREATE OR REPLACE FUNCTION isnt_strict( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _strict($1, $2), + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should not be strict' + ); +$$ LANGUAGE sql; + +-- isnt_strict( function, description ) +CREATE OR REPLACE FUNCTION isnt_strict( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, NOT _strict($1), $2 ); +$$ LANGUAGE sql; + +-- isnt_strict( function ) +CREATE OR REPLACE FUNCTION isnt_strict( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _strict($1), 'Function ' || quote_ident($1) || '() should not be strict' ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _expand_vol( char ) +RETURNS TEXT AS $$ + SELECT CASE $1 + WHEN 'i' THEN 'IMMUTABLE' + WHEN 's' THEN 'STABLE' + WHEN 'v' THEN 'VOLATILE' + ELSE 'UNKNOWN' END +$$ LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE FUNCTION _refine_vol( text ) +RETURNS text AS $$ + SELECT _expand_vol(substring(LOWER($1) FROM 1 FOR 1)::char); +$$ LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE FUNCTION _vol ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _expand_vol(volatility) + FROM tap_funky f + WHERE f.schema = $1 + and f.name = $2 + AND f.args = _funkargs($3) +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _vol ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT _expand_vol(volatility) FROM tap_funky f + WHERE f.schema = $1 and f.name = $2 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _vol ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _expand_vol(volatility) + FROM tap_funky f + WHERE f.name = $1 + AND f.args = _funkargs($2) + AND f.is_visible; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _vol ( NAME ) +RETURNS TEXT AS $$ + SELECT _expand_vol(volatility) FROM tap_funky f + WHERE f.name = $1 AND f.is_visible; +$$ LANGUAGE SQL; + +-- volatility_is( schema, function, args[], volatility, description ) +CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME, NAME[], TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, $3, _vol($1, $2, $3), _refine_vol($4), $5 ); +$$ LANGUAGE SQL; + +-- volatility_is( schema, function, args[], volatility ) +CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT volatility_is( + $1, $2, $3, $4, + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should be ' || _refine_vol($4) + ); +$$ LANGUAGE SQL; + +-- volatility_is( schema, function, volatility, description ) +CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, _vol($1, $2), _refine_vol($3), $4 ); +$$ LANGUAGE SQL; + +-- volatility_is( schema, function, volatility ) +CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT volatility_is( + $1, $2, $3, + 'Function ' || quote_ident($1) || '.' || quote_ident($2) + || '() should be ' || _refine_vol($3) + ); +$$ LANGUAGE SQL; + +-- volatility_is( function, args[], volatility, description ) +CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME[], TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, $2, _vol($1, $2), _refine_vol($3), $4 ); +$$ LANGUAGE SQL; + +-- volatility_is( function, args[], volatility ) +CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT volatility_is( + $1, $2, $3, + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should be ' || _refine_vol($3) + ); +$$ LANGUAGE SQL; + +-- volatility_is( function, volatility, description ) +CREATE OR REPLACE FUNCTION volatility_is( NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, _vol($1), _refine_vol($2), $3 ); +$$ LANGUAGE SQL; + +-- volatility_is( function, volatility ) +CREATE OR REPLACE FUNCTION volatility_is( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT volatility_is( + $1, $2, + 'Function ' || quote_ident($1) || '() should be ' || _refine_vol($2) + ); +$$ LANGUAGE SQL; + +-- check_test( test_output, pass, name, description, diag, match_diag ) +CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT, BOOLEAN ) +RETURNS SETOF TEXT AS $$ +DECLARE + tnumb INTEGER; + aok BOOLEAN; + adescr TEXT; + res BOOLEAN; + descr TEXT; + adiag TEXT; + have ALIAS FOR $1; + eok ALIAS FOR $2; + name ALIAS FOR $3; + edescr ALIAS FOR $4; + ediag ALIAS FOR $5; + matchit ALIAS FOR $6; +BEGIN + -- What test was it that just ran? + tnumb := currval('__tresults___numb_seq'); + + -- Fetch the results. + aok := substring(have, 1, 2) = 'ok'; + adescr := COALESCE(substring(have FROM E'(?:not )?ok [[:digit:]]+ - ([^\n]+)'), ''); + + -- Now delete those results. + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb; + IF NOT aok THEN PERFORM _set('failed', _get('failed') - 1); END IF; + + -- Set up the description. + descr := coalesce( name || ' ', 'Test ' ) || 'should '; + + -- So, did the test pass? + RETURN NEXT is( + aok, + eok, + descr || CASE eok WHEN true then 'pass' ELSE 'fail' END + ); + + -- Was the description as expected? + IF edescr IS NOT NULL THEN + RETURN NEXT is( + adescr, + edescr, + descr || 'have the proper description' + ); + END IF; + + -- Were the diagnostics as expected? + IF ediag IS NOT NULL THEN + -- Remove ok and the test number. + adiag := substring( + have + FROM CASE WHEN aok THEN 4 ELSE 9 END + char_length(tnumb::text) + ); + + -- Remove the description, if there is one. + IF adescr <> '' THEN + adiag := substring( + adiag FROM 1 + char_length( ' - ' || substr(diag( adescr ), 3) ) + ); + END IF; + + IF NOT aok THEN + -- Remove failure message from ok(). + adiag := substring(adiag FROM 1 + char_length(diag( + 'Failed test ' || tnumb || + CASE adescr WHEN '' THEN '' ELSE COALESCE(': "' || adescr || '"', '') END + ))); + END IF; + + IF ediag <> '' THEN + -- Remove the space before the diagnostics. + adiag := substring(adiag FROM 2); + END IF; + + -- Remove the #s. + adiag := replace( substring(adiag from 3), E'\n# ', E'\n' ); + + -- Now compare the diagnostics. + IF matchit THEN + RETURN NEXT matches( + adiag, + ediag, + descr || 'have the proper diagnostics' + ); + ELSE + RETURN NEXT is( + adiag, + ediag, + descr || 'have the proper diagnostics' + ); + END IF; + END IF; + + -- And we're done + RETURN; +END; +$$ LANGUAGE plpgsql; + +-- check_test( test_output, pass, name, description, diag ) +CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) +RETURNS SETOF TEXT AS $$ + SELECT * FROM check_test( $1, $2, $3, $4, $5, FALSE ); +$$ LANGUAGE sql; + +-- check_test( test_output, pass, name, description ) +CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ) +RETURNS SETOF TEXT AS $$ + SELECT * FROM check_test( $1, $2, $3, $4, NULL, FALSE ); +$$ LANGUAGE sql; + +-- check_test( test_output, pass, name ) +CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT ) +RETURNS SETOF TEXT AS $$ + SELECT * FROM check_test( $1, $2, $3, NULL, NULL, FALSE ); +$$ LANGUAGE sql; + +-- check_test( test_output, pass ) +CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN ) +RETURNS SETOF TEXT AS $$ + SELECT * FROM check_test( $1, $2, NULL, NULL, NULL, FALSE ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT, TEXT ) +RETURNS TEXT[] AS $$ + SELECT ARRAY( + SELECT DISTINCT (quote_ident(n.nspname) || '.' || quote_ident(p.proname)) COLLATE "C" AS pname + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid + WHERE n.nspname = $1 + AND p.proname ~ $2 + AND ($3 IS NULL OR p.proname !~ $3) + ORDER BY pname + ); +$$ LANGUAGE sql; +CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT ) +RETURNS TEXT[] AS $$ + SELECT findfuncs( $1, $2, NULL ) +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION findfuncs( TEXT, TEXT ) +RETURNS TEXT[] AS $$ + SELECT ARRAY( + SELECT DISTINCT (quote_ident(n.nspname) || '.' || quote_ident(p.proname)) COLLATE "C" AS pname + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid + WHERE pg_catalog.pg_function_is_visible(p.oid) + AND p.proname ~ $1 + AND ($2 IS NULL OR p.proname !~ $2) + ORDER BY pname + ); +$$ LANGUAGE sql; +CREATE OR REPLACE FUNCTION findfuncs( TEXT ) +RETURNS TEXT[] AS $$ + SELECT findfuncs( $1, NULL ) +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _runem( text[], boolean ) +RETURNS SETOF TEXT AS $$ +DECLARE + tap text; + lbound int := array_lower($1, 1); +BEGIN + IF lbound IS NULL THEN RETURN; END IF; + FOR i IN lbound..array_upper($1, 1) LOOP + -- Send the name of the function to diag if warranted. + IF $2 THEN RETURN NEXT diag( $1[i] || '()' ); END IF; + -- Execute the tap function and return its results. + FOR tap IN EXECUTE 'SELECT * FROM ' || $1[i] || '()' LOOP + RETURN NEXT tap; + END LOOP; + END LOOP; + RETURN; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _is_verbose() +RETURNS BOOLEAN AS $$ + SELECT current_setting('client_min_messages') NOT IN ( + 'warning', 'error', 'fatal', 'panic' + ); +$$ LANGUAGE sql STABLE; + +-- do_tap( schema, pattern ) +CREATE OR REPLACE FUNCTION do_tap( name, text ) +RETURNS SETOF TEXT AS $$ + SELECT * FROM _runem( findfuncs($1, $2), _is_verbose() ); +$$ LANGUAGE sql; + +-- do_tap( schema ) +CREATE OR REPLACE FUNCTION do_tap( name ) +RETURNS SETOF TEXT AS $$ + SELECT * FROM _runem( findfuncs($1, '^test'), _is_verbose() ); +$$ LANGUAGE sql; + +-- do_tap( pattern ) +CREATE OR REPLACE FUNCTION do_tap( text ) +RETURNS SETOF TEXT AS $$ + SELECT * FROM _runem( findfuncs($1), _is_verbose() ); +$$ LANGUAGE sql; + +-- do_tap() +CREATE OR REPLACE FUNCTION do_tap( ) +RETURNS SETOF TEXT AS $$ + SELECT * FROM _runem( findfuncs('^test'), _is_verbose()); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _currtest() +RETURNS INTEGER AS $$ +BEGIN + RETURN currval('__tresults___numb_seq'); +EXCEPTION + WHEN object_not_in_prerequisite_state THEN RETURN 0; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _cleanup() +RETURNS boolean AS $$ + DROP SEQUENCE __tresults___numb_seq; + DROP TABLE __tcache__; + DROP SEQUENCE __tcache___id_seq; + SELECT TRUE; +$$ LANGUAGE sql; + +-- diag_test_name ( test_name ) +CREATE OR REPLACE FUNCTION diag_test_name(TEXT) +RETURNS TEXT AS $$ + SELECT diag($1 || '()'); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _runner( text[], text[], text[], text[], text[] ) +RETURNS SETOF TEXT AS $$ +DECLARE + startup ALIAS FOR $1; + shutdown ALIAS FOR $2; + setup ALIAS FOR $3; + teardown ALIAS FOR $4; + tests ALIAS FOR $5; + tap TEXT; + tfaild INTEGER := 0; + ffaild INTEGER := 0; + tnumb INTEGER := 0; + fnumb INTEGER := 0; + tok BOOLEAN := TRUE; +BEGIN + BEGIN + -- No plan support. + PERFORM * FROM no_plan(); + FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; + EXCEPTION + -- Catch all exceptions and simply rethrow custom exceptions. This + -- will roll back everything in the above block. + WHEN raise_exception THEN RAISE EXCEPTION '%', SQLERRM; + END; + + -- Record how startup tests have failed. + tfaild := num_failed(); + + FOR i IN 1..COALESCE(array_upper(tests, 1), 0) LOOP + + -- What subtest are we running? + RETURN NEXT diag_test_name('Subtest: ' || tests[i]); + + -- Reset the results. + tok := TRUE; + tnumb := COALESCE(_get('curr_test'), 0); + + IF tnumb > 0 THEN + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; + PERFORM _set('curr_test', 0); + PERFORM _set('failed', 0); + END IF; + + DECLARE + errstate text; + errmsg text; + detail text; + hint text; + context text; + schname text; + tabname text; + colname text; + chkname text; + typname text; + BEGIN + BEGIN + -- Run the setup functions. + FOR tap IN SELECT * FROM _runem(setup, false) LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Run the actual test function. + FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Run the teardown functions. + FOR tap IN SELECT * FROM _runem(teardown, false) LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Emit the plan. + fnumb := COALESCE(_get('curr_test'), 0); + RETURN NEXT ' 1..' || fnumb; + + -- Emit any error messages. + IF fnumb = 0 THEN + RETURN NEXT ' # No tests run!'; + tok = false; + ELSE + -- Report failures. + ffaild := num_failed(); + IF ffaild > 0 THEN + tok := FALSE; + RETURN NEXT ' ' || diag( + 'Looks like you failed ' || ffaild || ' test' || + CASE ffaild WHEN 1 THEN '' ELSE 's' END + || ' of ' || fnumb + ); + END IF; + END IF; + + EXCEPTION WHEN OTHERS THEN + -- Something went wrong. Record that fact. + errstate := SQLSTATE; + errmsg := SQLERRM; + GET STACKED DIAGNOSTICS + detail = PG_EXCEPTION_DETAIL, + hint = PG_EXCEPTION_HINT, + context = PG_EXCEPTION_CONTEXT, + schname = SCHEMA_NAME, + tabname = TABLE_NAME, + colname = COLUMN_NAME, + chkname = CONSTRAINT_NAME, + typname = PG_DATATYPE_NAME; + END; + + -- Always raise an exception to rollback any changes. + RAISE EXCEPTION '__TAP_ROLLBACK__'; + + EXCEPTION WHEN raise_exception THEN + IF errmsg IS NOT NULL THEN + -- Something went wrong. Emit the error message. + tok := FALSE; + RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( + errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname + )), '^', ' ', 'gn'); + errmsg := NULL; + END IF; + END; + + -- Restore the sequence. + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; + PERFORM _set('curr_test', tnumb); + PERFORM _set('failed', tfaild); + + -- Record this test. + RETURN NEXT ok(tok, tests[i]); + IF NOT tok THEN tfaild := tfaild + 1; END IF; + + END LOOP; + + -- Run the shutdown functions. + FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; + + -- Finish up. + FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP + RETURN NEXT tap; + END LOOP; + + -- Clean up and return. + PERFORM _cleanup(); + RETURN; +END; +$$ LANGUAGE plpgsql; + +-- runtests( schema, match ) +CREATE OR REPLACE FUNCTION runtests( NAME, TEXT ) +RETURNS SETOF TEXT AS $$ + SELECT * FROM _runner( + findfuncs( $1, '^startup' ), + findfuncs( $1, '^shutdown' ), + findfuncs( $1, '^setup' ), + findfuncs( $1, '^teardown' ), + findfuncs( $1, $2, '^(startup|shutdown|setup|teardown)' ) + ); +$$ LANGUAGE sql; + +-- runtests( schema ) +CREATE OR REPLACE FUNCTION runtests( NAME ) +RETURNS SETOF TEXT AS $$ + SELECT * FROM runtests( $1, '^test' ); +$$ LANGUAGE sql; + +-- runtests( match ) +CREATE OR REPLACE FUNCTION runtests( TEXT ) +RETURNS SETOF TEXT AS $$ + SELECT * FROM _runner( + findfuncs( '^startup' ), + findfuncs( '^shutdown' ), + findfuncs( '^setup' ), + findfuncs( '^teardown' ), + findfuncs( $1, '^(startup|shutdown|setup|teardown)' ) + ); +$$ LANGUAGE sql; + +-- runtests( ) +CREATE OR REPLACE FUNCTION runtests( ) +RETURNS SETOF TEXT AS $$ + SELECT * FROM runtests( '^test' ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _temptable ( TEXT, TEXT ) +RETURNS TEXT AS $$ +BEGIN + EXECUTE 'CREATE TEMP TABLE ' || $2 || ' AS ' || _query($1); + return $2; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _temptable ( anyarray, TEXT ) +RETURNS TEXT AS $$ +BEGIN + CREATE TEMP TABLE _____coltmp___ AS + SELECT $1[i] + FROM generate_series(array_lower($1, 1), array_upper($1, 1)) s(i); + EXECUTE 'ALTER TABLE _____coltmp___ RENAME TO ' || $2; + return $2; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _temptypes( TEXT ) +RETURNS TEXT AS $$ + SELECT array_to_string(ARRAY( + SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) + FROM pg_catalog.pg_attribute a + JOIN pg_catalog.pg_class c ON a.attrelid = c.oid + WHERE c.oid = ('pg_temp.' || $1)::pg_catalog.regclass + AND attnum > 0 + AND NOT attisdropped + ORDER BY attnum + ), ','); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _docomp( TEXT, TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + have ALIAS FOR $1; + want ALIAS FOR $2; + extras TEXT[] := '{}'; + missing TEXT[] := '{}'; + res BOOLEAN := TRUE; + msg TEXT := ''; + rec RECORD; +BEGIN + BEGIN + -- Find extra records. + FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 + || 'SELECT * FROM ' || want LOOP + extras := extras || rec::text; + END LOOP; + + -- Find missing records. + FOR rec in EXECUTE 'SELECT * FROM ' || want || ' EXCEPT ' || $4 + || 'SELECT * FROM ' || have LOOP + missing := missing || rec::text; + END LOOP; + + -- Drop the temporary tables. + EXECUTE 'DROP TABLE ' || have; + EXECUTE 'DROP TABLE ' || want; + EXCEPTION WHEN syntax_error OR datatype_mismatch THEN + msg := E'\n' || diag( + E' Columns differ between queries:\n' + || ' have: (' || _temptypes(have) || E')\n' + || ' want: (' || _temptypes(want) || ')' + ); + EXECUTE 'DROP TABLE ' || have; + EXECUTE 'DROP TABLE ' || want; + RETURN ok(FALSE, $3) || msg; + END; + + -- What extra records do we have? + IF extras[1] IS NOT NULL THEN + res := FALSE; + msg := E'\n' || diag( + E' Extra records:\n ' + || array_to_string( extras, E'\n ' ) + ); + END IF; + + -- What missing records do we have? + IF missing[1] IS NOT NULL THEN + res := FALSE; + msg := msg || E'\n' || diag( + E' Missing records:\n ' + || array_to_string( missing, E'\n ' ) + ); + END IF; + + RETURN ok(res, $3) || msg; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _docomp( + _temptable( $1, '__taphave__' ), + _temptable( $2, '__tapwant__' ), + $3, $4 + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _relcomp( TEXT, anyarray, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _docomp( + _temptable( $1, '__taphave__' ), + _temptable( $2, '__tapwant__' ), + $3, $4 + ); +$$ LANGUAGE sql; + +-- set_eq( sql, sql, description ) +CREATE OR REPLACE FUNCTION set_eq( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _relcomp( $1, $2, $3, '' ); +$$ LANGUAGE sql; + +-- set_eq( sql, sql ) +CREATE OR REPLACE FUNCTION set_eq( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _relcomp( $1, $2, NULL::text, '' ); +$$ LANGUAGE sql; + +-- set_eq( sql, array, description ) +CREATE OR REPLACE FUNCTION set_eq( TEXT, anyarray, TEXT ) +RETURNS TEXT AS $$ + SELECT _relcomp( $1, $2, $3, '' ); +$$ LANGUAGE sql; + +-- set_eq( sql, array ) +CREATE OR REPLACE FUNCTION set_eq( TEXT, anyarray ) +RETURNS TEXT AS $$ + SELECT _relcomp( $1, $2, NULL::text, '' ); +$$ LANGUAGE sql; + +-- bag_eq( sql, sql, description ) +CREATE OR REPLACE FUNCTION bag_eq( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _relcomp( $1, $2, $3, 'ALL ' ); +$$ LANGUAGE sql; + +-- bag_eq( sql, sql ) +CREATE OR REPLACE FUNCTION bag_eq( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _relcomp( $1, $2, NULL::text, 'ALL ' ); +$$ LANGUAGE sql; + +-- bag_eq( sql, array, description ) +CREATE OR REPLACE FUNCTION bag_eq( TEXT, anyarray, TEXT ) +RETURNS TEXT AS $$ + SELECT _relcomp( $1, $2, $3, 'ALL ' ); +$$ LANGUAGE sql; + +-- bag_eq( sql, array ) +CREATE OR REPLACE FUNCTION bag_eq( TEXT, anyarray ) +RETURNS TEXT AS $$ + SELECT _relcomp( $1, $2, NULL::text, 'ALL ' ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _do_ne( TEXT, TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + have ALIAS FOR $1; + want ALIAS FOR $2; + extras TEXT[] := '{}'; + missing TEXT[] := '{}'; + res BOOLEAN := TRUE; + msg TEXT := ''; +BEGIN + BEGIN + -- Find extra records. + EXECUTE 'SELECT EXISTS ( ' + || '( SELECT * FROM ' || have || ' EXCEPT ' || $4 + || ' SELECT * FROM ' || want + || ' ) UNION ( ' + || ' SELECT * FROM ' || want || ' EXCEPT ' || $4 + || ' SELECT * FROM ' || have + || ' ) LIMIT 1 )' INTO res; + + -- Drop the temporary tables. + EXECUTE 'DROP TABLE ' || have; + EXECUTE 'DROP TABLE ' || want; + EXCEPTION WHEN syntax_error OR datatype_mismatch THEN + msg := E'\n' || diag( + E' Columns differ between queries:\n' + || ' have: (' || _temptypes(have) || E')\n' + || ' want: (' || _temptypes(want) || ')' + ); + EXECUTE 'DROP TABLE ' || have; + EXECUTE 'DROP TABLE ' || want; + RETURN ok(FALSE, $3) || msg; + END; + + -- Return the value from the query. + RETURN ok(res, $3); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _relne( TEXT, TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _do_ne( + _temptable( $1, '__taphave__' ), + _temptable( $2, '__tapwant__' ), + $3, $4 + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _relne( TEXT, anyarray, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _do_ne( + _temptable( $1, '__taphave__' ), + _temptable( $2, '__tapwant__' ), + $3, $4 + ); +$$ LANGUAGE sql; + +-- set_ne( sql, sql, description ) +CREATE OR REPLACE FUNCTION set_ne( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _relne( $1, $2, $3, '' ); +$$ LANGUAGE sql; + +-- set_ne( sql, sql ) +CREATE OR REPLACE FUNCTION set_ne( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _relne( $1, $2, NULL::text, '' ); +$$ LANGUAGE sql; + +-- set_ne( sql, array, description ) +CREATE OR REPLACE FUNCTION set_ne( TEXT, anyarray, TEXT ) +RETURNS TEXT AS $$ + SELECT _relne( $1, $2, $3, '' ); +$$ LANGUAGE sql; + +-- set_ne( sql, array ) +CREATE OR REPLACE FUNCTION set_ne( TEXT, anyarray ) +RETURNS TEXT AS $$ + SELECT _relne( $1, $2, NULL::text, '' ); +$$ LANGUAGE sql; + +-- bag_ne( sql, sql, description ) +CREATE OR REPLACE FUNCTION bag_ne( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _relne( $1, $2, $3, 'ALL ' ); +$$ LANGUAGE sql; + +-- bag_ne( sql, sql ) +CREATE OR REPLACE FUNCTION bag_ne( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _relne( $1, $2, NULL::text, 'ALL ' ); +$$ LANGUAGE sql; + +-- bag_ne( sql, array, description ) +CREATE OR REPLACE FUNCTION bag_ne( TEXT, anyarray, TEXT ) +RETURNS TEXT AS $$ + SELECT _relne( $1, $2, $3, 'ALL ' ); +$$ LANGUAGE sql; + +-- bag_ne( sql, array ) +CREATE OR REPLACE FUNCTION bag_ne( TEXT, anyarray ) +RETURNS TEXT AS $$ + SELECT _relne( $1, $2, NULL::text, 'ALL ' ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + have TEXT := _temptable( $1, '__taphave__' ); + want TEXT := _temptable( $2, '__tapwant__' ); + results TEXT[] := '{}'; + res BOOLEAN := TRUE; + msg TEXT := ''; + rec RECORD; +BEGIN + BEGIN + -- Find relevant records. + FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 + || ' SELECT * FROM ' || have LOOP + results := results || rec::text; + END LOOP; + + -- Drop the temporary tables. + EXECUTE 'DROP TABLE ' || have; + EXECUTE 'DROP TABLE ' || want; + EXCEPTION WHEN syntax_error OR datatype_mismatch THEN + msg := E'\n' || diag( + E' Columns differ between queries:\n' + || ' have: (' || _temptypes(have) || E')\n' + || ' want: (' || _temptypes(want) || ')' + ); + EXECUTE 'DROP TABLE ' || have; + EXECUTE 'DROP TABLE ' || want; + RETURN ok(FALSE, $3) || msg; + END; + + -- What records do we have? + IF results[1] IS NOT NULL THEN + res := FALSE; + msg := msg || E'\n' || diag( + ' ' || $5 || E' records:\n ' + || array_to_string( results, E'\n ' ) + ); + END IF; + + RETURN ok(res, $3) || msg; +END; +$$ LANGUAGE plpgsql; + +-- set_has( sql, sql, description ) +CREATE OR REPLACE FUNCTION set_has( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _relcomp( $1, $2, $3, 'EXCEPT', 'Missing' ); +$$ LANGUAGE sql; + +-- set_has( sql, sql ) +CREATE OR REPLACE FUNCTION set_has( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _relcomp( $1, $2, NULL::TEXT, 'EXCEPT', 'Missing' ); +$$ LANGUAGE sql; + +-- bag_has( sql, sql, description ) +CREATE OR REPLACE FUNCTION bag_has( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _relcomp( $1, $2, $3, 'EXCEPT ALL', 'Missing' ); +$$ LANGUAGE sql; + +-- bag_has( sql, sql ) +CREATE OR REPLACE FUNCTION bag_has( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _relcomp( $1, $2, NULL::TEXT, 'EXCEPT ALL', 'Missing' ); +$$ LANGUAGE sql; + +-- set_hasnt( sql, sql, description ) +CREATE OR REPLACE FUNCTION set_hasnt( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _relcomp( $1, $2, $3, 'INTERSECT', 'Extra' ); +$$ LANGUAGE sql; + +-- set_hasnt( sql, sql ) +CREATE OR REPLACE FUNCTION set_hasnt( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _relcomp( $1, $2, NULL::TEXT, 'INTERSECT', 'Extra' ); +$$ LANGUAGE sql; + +-- bag_hasnt( sql, sql, description ) +CREATE OR REPLACE FUNCTION bag_hasnt( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _relcomp( $1, $2, $3, 'INTERSECT ALL', 'Extra' ); +$$ LANGUAGE sql; + +-- bag_hasnt( sql, sql ) +CREATE OR REPLACE FUNCTION bag_hasnt( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT _relcomp( $1, $2, NULL::TEXT, 'INTERSECT ALL', 'Extra' ); +$$ LANGUAGE sql; + +-- results_eq( cursor, cursor, description ) +CREATE OR REPLACE FUNCTION results_eq( refcursor, refcursor, text ) +RETURNS TEXT AS $$ +DECLARE + have ALIAS FOR $1; + want ALIAS FOR $2; + have_rec RECORD; + want_rec RECORD; + have_found BOOLEAN; + want_found BOOLEAN; + rownum INTEGER := 1; + err_msg text := 'details not available in pg <= 9.1'; +BEGIN + FETCH have INTO have_rec; + have_found := FOUND; + FETCH want INTO want_rec; + want_found := FOUND; + WHILE have_found OR want_found LOOP + IF have_rec IS DISTINCT FROM want_rec OR have_found <> want_found THEN + RETURN ok( false, $3 ) || E'\n' || diag( + ' Results differ beginning at row ' || rownum || E':\n' || + ' have: ' || CASE WHEN have_found THEN have_rec::text ELSE 'NULL' END || E'\n' || + ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE 'NULL' END + ); + END IF; + rownum = rownum + 1; + FETCH have INTO have_rec; + have_found := FOUND; + FETCH want INTO want_rec; + want_found := FOUND; + END LOOP; + + RETURN ok( true, $3 ); +EXCEPTION + WHEN datatype_mismatch THEN + GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT; + RETURN ok( false, $3 ) || E'\n' || diag( + E' Number of columns or their types differ between the queries' || + CASE WHEN have_rec::TEXT = want_rec::text THEN '' ELSE E':\n' || + ' have: ' || CASE WHEN have_found THEN have_rec::text ELSE 'NULL' END || E'\n' || + ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE 'NULL' END + END || E'\n ERROR: ' || err_msg + ); +END; +$$ LANGUAGE plpgsql; + +-- results_eq( cursor, cursor ) +CREATE OR REPLACE FUNCTION results_eq( refcursor, refcursor ) +RETURNS TEXT AS $$ + SELECT results_eq( $1, $2, NULL::text ); +$$ LANGUAGE sql; + +-- results_eq( sql, sql, description ) +CREATE OR REPLACE FUNCTION results_eq( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + have REFCURSOR; + want REFCURSOR; + res TEXT; +BEGIN + OPEN have FOR EXECUTE _query($1); + OPEN want FOR EXECUTE _query($2); + res := results_eq(have, want, $3); + CLOSE have; + CLOSE want; + RETURN res; +END; +$$ LANGUAGE plpgsql; + +-- results_eq( sql, sql ) +CREATE OR REPLACE FUNCTION results_eq( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT results_eq( $1, $2, NULL::text ); +$$ LANGUAGE sql; + +-- results_eq( sql, array, description ) +CREATE OR REPLACE FUNCTION results_eq( TEXT, anyarray, TEXT ) +RETURNS TEXT AS $$ +DECLARE + have REFCURSOR; + want REFCURSOR; + res TEXT; +BEGIN + OPEN have FOR EXECUTE _query($1); + OPEN want FOR SELECT $2[i] + FROM generate_series(array_lower($2, 1), array_upper($2, 1)) s(i); + res := results_eq(have, want, $3); + CLOSE have; + CLOSE want; + RETURN res; +END; +$$ LANGUAGE plpgsql; + +-- results_eq( sql, array ) +CREATE OR REPLACE FUNCTION results_eq( TEXT, anyarray ) +RETURNS TEXT AS $$ + SELECT results_eq( $1, $2, NULL::text ); +$$ LANGUAGE sql; + +-- results_eq( sql, cursor, description ) +CREATE OR REPLACE FUNCTION results_eq( TEXT, refcursor, TEXT ) +RETURNS TEXT AS $$ +DECLARE + have REFCURSOR; + res TEXT; +BEGIN + OPEN have FOR EXECUTE _query($1); + res := results_eq(have, $2, $3); + CLOSE have; + RETURN res; +END; +$$ LANGUAGE plpgsql; + +-- results_eq( sql, cursor ) +CREATE OR REPLACE FUNCTION results_eq( TEXT, refcursor ) +RETURNS TEXT AS $$ + SELECT results_eq( $1, $2, NULL::text ); +$$ LANGUAGE sql; + +-- results_eq( cursor, sql, description ) +CREATE OR REPLACE FUNCTION results_eq( refcursor, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + want REFCURSOR; + res TEXT; +BEGIN + OPEN want FOR EXECUTE _query($2); + res := results_eq($1, want, $3); + CLOSE want; + RETURN res; +END; +$$ LANGUAGE plpgsql; + +-- results_eq( cursor, sql ) +CREATE OR REPLACE FUNCTION results_eq( refcursor, TEXT ) +RETURNS TEXT AS $$ + SELECT results_eq( $1, $2, NULL::text ); +$$ LANGUAGE sql; + +-- results_eq( cursor, array, description ) +CREATE OR REPLACE FUNCTION results_eq( refcursor, anyarray, TEXT ) +RETURNS TEXT AS $$ +DECLARE + want REFCURSOR; + res TEXT; +BEGIN + OPEN want FOR SELECT $2[i] + FROM generate_series(array_lower($2, 1), array_upper($2, 1)) s(i); + res := results_eq($1, want, $3); + CLOSE want; + RETURN res; +END; +$$ LANGUAGE plpgsql; + +-- results_eq( cursor, array ) +CREATE OR REPLACE FUNCTION results_eq( refcursor, anyarray ) +RETURNS TEXT AS $$ + SELECT results_eq( $1, $2, NULL::text ); +$$ LANGUAGE sql; + +-- results_ne( cursor, cursor, description ) +CREATE OR REPLACE FUNCTION results_ne( refcursor, refcursor, text ) +RETURNS TEXT AS $$ +DECLARE + have ALIAS FOR $1; + want ALIAS FOR $2; + have_rec RECORD; + want_rec RECORD; + have_found BOOLEAN; + want_found BOOLEAN; + err_msg text := 'details not available in pg <= 9.1'; +BEGIN + FETCH have INTO have_rec; + have_found := FOUND; + FETCH want INTO want_rec; + want_found := FOUND; + WHILE have_found OR want_found LOOP + IF have_rec IS DISTINCT FROM want_rec OR have_found <> want_found THEN + RETURN ok( true, $3 ); + ELSE + FETCH have INTO have_rec; + have_found := FOUND; + FETCH want INTO want_rec; + want_found := FOUND; + END IF; + END LOOP; + RETURN ok( false, $3 ); +EXCEPTION + WHEN datatype_mismatch THEN + GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT; + RETURN ok( false, $3 ) || E'\n' || diag( + E' Number of columns or their types differ between the queries' || + CASE WHEN have_rec::TEXT = want_rec::text THEN '' ELSE E':\n' || + ' have: ' || CASE WHEN have_found THEN have_rec::text ELSE 'NULL' END || E'\n' || + ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE 'NULL' END + END || E'\n ERROR: ' || err_msg + ); +END; +$$ LANGUAGE plpgsql; + +-- results_ne( cursor, cursor ) +CREATE OR REPLACE FUNCTION results_ne( refcursor, refcursor ) +RETURNS TEXT AS $$ + SELECT results_ne( $1, $2, NULL::text ); +$$ LANGUAGE sql; + +-- results_ne( sql, sql, description ) +CREATE OR REPLACE FUNCTION results_ne( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + have REFCURSOR; + want REFCURSOR; + res TEXT; +BEGIN + OPEN have FOR EXECUTE _query($1); + OPEN want FOR EXECUTE _query($2); + res := results_ne(have, want, $3); + CLOSE have; + CLOSE want; + RETURN res; +END; +$$ LANGUAGE plpgsql; + +-- results_ne( sql, sql ) +CREATE OR REPLACE FUNCTION results_ne( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT results_ne( $1, $2, NULL::text ); +$$ LANGUAGE sql; + +-- results_ne( sql, array, description ) +CREATE OR REPLACE FUNCTION results_ne( TEXT, anyarray, TEXT ) +RETURNS TEXT AS $$ +DECLARE + have REFCURSOR; + want REFCURSOR; + res TEXT; +BEGIN + OPEN have FOR EXECUTE _query($1); + OPEN want FOR SELECT $2[i] + FROM generate_series(array_lower($2, 1), array_upper($2, 1)) s(i); + res := results_ne(have, want, $3); + CLOSE have; + CLOSE want; + RETURN res; +END; +$$ LANGUAGE plpgsql; + +-- results_ne( sql, array ) +CREATE OR REPLACE FUNCTION results_ne( TEXT, anyarray ) +RETURNS TEXT AS $$ + SELECT results_ne( $1, $2, NULL::text ); +$$ LANGUAGE sql; + +-- results_ne( sql, cursor, description ) +CREATE OR REPLACE FUNCTION results_ne( TEXT, refcursor, TEXT ) +RETURNS TEXT AS $$ +DECLARE + have REFCURSOR; + res TEXT; +BEGIN + OPEN have FOR EXECUTE _query($1); + res := results_ne(have, $2, $3); + CLOSE have; + RETURN res; +END; +$$ LANGUAGE plpgsql; + +-- results_ne( sql, cursor ) +CREATE OR REPLACE FUNCTION results_ne( TEXT, refcursor ) +RETURNS TEXT AS $$ + SELECT results_ne( $1, $2, NULL::text ); +$$ LANGUAGE sql; + +-- results_ne( cursor, sql, description ) +CREATE OR REPLACE FUNCTION results_ne( refcursor, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + want REFCURSOR; + res TEXT; +BEGIN + OPEN want FOR EXECUTE _query($2); + res := results_ne($1, want, $3); + CLOSE want; + RETURN res; +END; +$$ LANGUAGE plpgsql; + +-- results_ne( cursor, sql ) +CREATE OR REPLACE FUNCTION results_ne( refcursor, TEXT ) +RETURNS TEXT AS $$ + SELECT results_ne( $1, $2, NULL::text ); +$$ LANGUAGE sql; + +-- results_ne( cursor, array, description ) +CREATE OR REPLACE FUNCTION results_ne( refcursor, anyarray, TEXT ) +RETURNS TEXT AS $$ +DECLARE + want REFCURSOR; + res TEXT; +BEGIN + OPEN want FOR SELECT $2[i] + FROM generate_series(array_lower($2, 1), array_upper($2, 1)) s(i); + res := results_ne($1, want, $3); + CLOSE want; + RETURN res; +END; +$$ LANGUAGE plpgsql; + +-- results_ne( cursor, array ) +CREATE OR REPLACE FUNCTION results_ne( refcursor, anyarray ) +RETURNS TEXT AS $$ + SELECT results_ne( $1, $2, NULL::text ); +$$ LANGUAGE sql; + +-- isa_ok( value, regtype, description ) +CREATE OR REPLACE FUNCTION isa_ok( anyelement, regtype, TEXT ) +RETURNS TEXT AS $$ +DECLARE + typeof regtype := pg_typeof($1); +BEGIN + IF typeof = $2 THEN RETURN ok(true, $3 || ' isa ' || $2 ); END IF; + RETURN ok(false, $3 || ' isa ' || $2 ) || E'\n' || + diag(' ' || $3 || ' isn''t a "' || $2 || '" it''s a "' || typeof || '"'); +END; +$$ LANGUAGE plpgsql; + +-- isa_ok( value, regtype ) +CREATE OR REPLACE FUNCTION isa_ok( anyelement, regtype ) +RETURNS TEXT AS $$ + SELECT isa_ok($1, $2, 'the value'); +$$ LANGUAGE sql; + +-- is_empty( sql, description ) +CREATE OR REPLACE FUNCTION is_empty( TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + extras TEXT[] := '{}'; + res BOOLEAN := TRUE; + msg TEXT := ''; + rec RECORD; +BEGIN + -- Find extra records. + FOR rec in EXECUTE _query($1) LOOP + extras := extras || rec::text; + END LOOP; + + -- What extra records do we have? + IF extras[1] IS NOT NULL THEN + res := FALSE; + msg := E'\n' || diag( + E' Unexpected records:\n ' + || array_to_string( extras, E'\n ' ) + ); + END IF; + + RETURN ok(res, $2) || msg; +END; +$$ LANGUAGE plpgsql; + +-- is_empty( sql ) +CREATE OR REPLACE FUNCTION is_empty( TEXT ) +RETURNS TEXT AS $$ + SELECT is_empty( $1, NULL ); +$$ LANGUAGE sql; + +-- isnt_empty( sql, description ) +CREATE OR REPLACE FUNCTION isnt_empty( TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + res BOOLEAN := FALSE; + rec RECORD; +BEGIN + -- Find extra records. + FOR rec in EXECUTE _query($1) LOOP + res := TRUE; + EXIT; + END LOOP; + + RETURN ok(res, $2); +END; +$$ LANGUAGE plpgsql; + +-- isnt_empty( sql ) +CREATE OR REPLACE FUNCTION isnt_empty( TEXT ) +RETURNS TEXT AS $$ + SELECT isnt_empty( $1, NULL ); +$$ LANGUAGE sql; + +-- collect_tap( tap, tap, tap ) +CREATE OR REPLACE FUNCTION collect_tap( VARIADIC text[] ) +RETURNS TEXT AS $$ + SELECT array_to_string($1, E'\n'); +$$ LANGUAGE sql; + +-- collect_tap( tap[] ) +CREATE OR REPLACE FUNCTION collect_tap( VARCHAR[] ) +RETURNS TEXT AS $$ + SELECT array_to_string($1, E'\n'); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( $1, $4 ) || CASE WHEN $1 THEN '' ELSE E'\n' || diag( + ' error message: ' || COALESCE( quote_literal($2), 'NULL' ) || + E'\n doesn''t match: ' || COALESCE( quote_literal($3), 'NULL' ) + ) END; +$$ LANGUAGE sql; + +-- throws_like ( sql, pattern, description ) +CREATE OR REPLACE FUNCTION throws_like ( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +BEGIN + EXECUTE _query($1); + RETURN ok( FALSE, $3 ) || E'\n' || diag( ' no exception thrown' ); +EXCEPTION WHEN OTHERS THEN + return _tlike( SQLERRM ~~ $2, SQLERRM, $2, $3 ); +END; +$$ LANGUAGE plpgsql; + +-- throws_like ( sql, pattern ) +CREATE OR REPLACE FUNCTION throws_like ( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT throws_like($1, $2, 'Should throw exception like ' || quote_literal($2) ); +$$ LANGUAGE sql; + +-- throws_ilike ( sql, pattern, description ) +CREATE OR REPLACE FUNCTION throws_ilike ( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +BEGIN + EXECUTE _query($1); + RETURN ok( FALSE, $3 ) || E'\n' || diag( ' no exception thrown' ); +EXCEPTION WHEN OTHERS THEN + return _tlike( SQLERRM ~~* $2, SQLERRM, $2, $3 ); +END; +$$ LANGUAGE plpgsql; + +-- throws_ilike ( sql, pattern ) +CREATE OR REPLACE FUNCTION throws_ilike ( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT throws_ilike($1, $2, 'Should throw exception like ' || quote_literal($2) ); +$$ LANGUAGE sql; + +-- throws_matching ( sql, pattern, description ) +CREATE OR REPLACE FUNCTION throws_matching ( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +BEGIN + EXECUTE _query($1); + RETURN ok( FALSE, $3 ) || E'\n' || diag( ' no exception thrown' ); +EXCEPTION WHEN OTHERS THEN + return _tlike( SQLERRM ~ $2, SQLERRM, $2, $3 ); +END; +$$ LANGUAGE plpgsql; + +-- throws_matching ( sql, pattern ) +CREATE OR REPLACE FUNCTION throws_matching ( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT throws_matching($1, $2, 'Should throw exception matching ' || quote_literal($2) ); +$$ LANGUAGE sql; + +-- throws_imatching ( sql, pattern, description ) +CREATE OR REPLACE FUNCTION throws_imatching ( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +BEGIN + EXECUTE _query($1); + RETURN ok( FALSE, $3 ) || E'\n' || diag( ' no exception thrown' ); +EXCEPTION WHEN OTHERS THEN + return _tlike( SQLERRM ~* $2, SQLERRM, $2, $3 ); +END; +$$ LANGUAGE plpgsql; + +-- throws_imatching ( sql, pattern ) +CREATE OR REPLACE FUNCTION throws_imatching ( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT throws_imatching($1, $2, 'Should throw exception matching ' || quote_literal($2) ); +$$ LANGUAGE sql; + +-- roles_are( roles[], description ) +CREATE OR REPLACE FUNCTION roles_are( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'roles', + ARRAY( + SELECT rolname + FROM pg_catalog.pg_roles + EXCEPT + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + ), + ARRAY( + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + EXCEPT + SELECT rolname + FROM pg_catalog.pg_roles + ), + $2 + ); +$$ LANGUAGE SQL; + +-- roles_are( roles[] ) +CREATE OR REPLACE FUNCTION roles_are( NAME[] ) +RETURNS TEXT AS $$ + SELECT roles_are( $1, 'There should be the correct roles' ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _types_are ( NAME, NAME[], TEXT, CHAR[] ) +RETURNS TEXT AS $$ + SELECT _are( + 'types', + ARRAY( + SELECT t.typname + FROM pg_catalog.pg_type t + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace + WHERE ( + t.typrelid = 0 + OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) + ) + AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid) + AND n.nspname = $1 + AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) + EXCEPT + SELECT _typename($2[i]) + FROM generate_series(1, array_upper($2, 1)) s(i) + ), + ARRAY( + SELECT _typename($2[i]) + FROM generate_series(1, array_upper($2, 1)) s(i) + EXCEPT + SELECT t.typname + FROM pg_catalog.pg_type t + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace + WHERE ( + t.typrelid = 0 + OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) + ) + AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid) + AND n.nspname = $1 + AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) + ), + $3 + ); +$$ LANGUAGE SQL; + +-- types_are( schema, types[], description ) +CREATE OR REPLACE FUNCTION types_are ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _types_are( $1, $2, $3, NULL ); +$$ LANGUAGE SQL; + +-- types_are( schema, types[] ) +CREATE OR REPLACE FUNCTION types_are ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _types_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct types', NULL ); +$$ LANGUAGE SQL; + +-- types_are( types[], description ) +CREATE OR REPLACE FUNCTION _types_are ( NAME[], TEXT, CHAR[] ) +RETURNS TEXT AS $$ + SELECT _are( + 'types', + ARRAY( + SELECT t.typname + FROM pg_catalog.pg_type t + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace + WHERE ( + t.typrelid = 0 + OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) + ) + AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid) + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + AND pg_catalog.pg_type_is_visible(t.oid) + AND t.typtype = ANY( COALESCE($3, ARRAY['b', 'c', 'd', 'p', 'e']) ) + EXCEPT + SELECT _typename($1[i]) + FROM generate_series(1, array_upper($1, 1)) s(i) + ), + ARRAY( + SELECT _typename($1[i]) + FROM generate_series(1, array_upper($1, 1)) s(i) + EXCEPT + SELECT t.typname + FROM pg_catalog.pg_type t + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace + WHERE ( + t.typrelid = 0 + OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) + ) + AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid) + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + AND pg_catalog.pg_type_is_visible(t.oid) + AND t.typtype = ANY( COALESCE($3, ARRAY['b', 'c', 'd', 'p', 'e']) ) + ), + $2 + ); +$$ LANGUAGE SQL; + +-- types_are( types[], description ) +CREATE OR REPLACE FUNCTION types_are ( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _types_are( $1, $2, NULL ); +$$ LANGUAGE SQL; + +-- types_are( types[] ) +CREATE OR REPLACE FUNCTION types_are ( NAME[] ) +RETURNS TEXT AS $$ + SELECT _types_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct types', NULL ); +$$ LANGUAGE SQL; + +-- domains_are( schema, domains[], description ) +CREATE OR REPLACE FUNCTION domains_are ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _types_are( $1, $2, $3, ARRAY['d'] ); +$$ LANGUAGE SQL; + +-- domains_are( schema, domains[] ) +CREATE OR REPLACE FUNCTION domains_are ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _types_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct domains', ARRAY['d'] ); +$$ LANGUAGE SQL; + +-- domains_are( domains[], description ) +CREATE OR REPLACE FUNCTION domains_are ( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _types_are( $1, $2, ARRAY['d'] ); +$$ LANGUAGE SQL; + +-- domains_are( domains[] ) +CREATE OR REPLACE FUNCTION domains_are ( NAME[] ) +RETURNS TEXT AS $$ + SELECT _types_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct domains', ARRAY['d'] ); +$$ LANGUAGE SQL; + +-- enums_are( schema, enums[], description ) +CREATE OR REPLACE FUNCTION enums_are ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _types_are( $1, $2, $3, ARRAY['e'] ); +$$ LANGUAGE SQL; + +-- enums_are( schema, enums[] ) +CREATE OR REPLACE FUNCTION enums_are ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _types_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct enums', ARRAY['e'] ); +$$ LANGUAGE SQL; + +-- enums_are( enums[], description ) +CREATE OR REPLACE FUNCTION enums_are ( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _types_are( $1, $2, ARRAY['e'] ); +$$ LANGUAGE SQL; + +-- enums_are( enums[] ) +CREATE OR REPLACE FUNCTION enums_are ( NAME[] ) +RETURNS TEXT AS $$ + SELECT _types_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct enums', ARRAY['e'] ); +$$ LANGUAGE SQL; + +-- _dexists( schema, domain ) +CREATE OR REPLACE FUNCTION _dexists ( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_type t on n.oid = t.typnamespace + WHERE n.nspname = $1 + AND t.typname = $2 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _dexists ( NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_type t + WHERE t.typname = $1 + AND pg_catalog.pg_type_is_visible(t.oid) + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_dtype( NAME, TEXT, BOOLEAN ) +RETURNS TEXT AS $$ + SELECT CASE WHEN $3 AND pg_catalog.pg_type_is_visible(t.oid) + THEN quote_ident(tn.nspname) || '.' + ELSE '' + END || pg_catalog.format_type(t.oid, t.typtypmod) + FROM pg_catalog.pg_type d + JOIN pg_catalog.pg_namespace dn ON d.typnamespace = dn.oid + JOIN pg_catalog.pg_type t ON d.typbasetype = t.oid + JOIN pg_catalog.pg_namespace tn ON t.typnamespace = tn.oid + WHERE d.typisdefined + AND dn.nspname = $1 + AND d.typname = LOWER($2) + AND d.typtype = 'd' +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _get_dtype( NAME ) +RETURNS TEXT AS $$ + SELECT pg_catalog.format_type(t.oid, t.typtypmod) + FROM pg_catalog.pg_type d + JOIN pg_catalog.pg_type t ON d.typbasetype = t.oid + WHERE d.typisdefined + AND pg_catalog.pg_type_is_visible(d.oid) + AND d.typname = LOWER($1) + AND d.typtype = 'd' +$$ LANGUAGE sql; + +-- domain_type_is( schema, domain, schema, type, description ) +CREATE OR REPLACE FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + actual_type TEXT := _get_dtype($1, $2, true); +BEGIN + IF actual_type IS NULL THEN + RETURN fail( $5 ) || E'\n' || diag ( + ' Domain ' || quote_ident($1) || '.' || $2 + || ' does not exist' + ); + END IF; + + IF quote_ident($3) = ANY(current_schemas(true)) THEN + RETURN is( actual_type, quote_ident($3) || '.' || _typename($4), $5); + END IF; + RETURN is( actual_type, _typename(quote_ident($3) || '.' || $4), $5); +END; +$$ LANGUAGE plpgsql; + +-- domain_type_is( schema, domain, schema, type ) +CREATE OR REPLACE FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT domain_type_is( + $1, $2, $3, $4, + 'Domain ' || quote_ident($1) || '.' || $2 + || ' should extend type ' || quote_ident($3) || '.' || $4 + ); +$$ LANGUAGE SQL; + +-- domain_type_is( schema, domain, type, description ) +CREATE OR REPLACE FUNCTION domain_type_is( NAME, TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + actual_type TEXT := _get_dtype($1, $2, false); +BEGIN + IF actual_type IS NULL THEN + RETURN fail( $4 ) || E'\n' || diag ( + ' Domain ' || quote_ident($1) || '.' || $2 + || ' does not exist' + ); + END IF; + + RETURN is( actual_type, _typename($3), $4 ); +END; +$$ LANGUAGE plpgsql; + +-- domain_type_is( schema, domain, type ) +CREATE OR REPLACE FUNCTION domain_type_is( NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT domain_type_is( + $1, $2, $3, + 'Domain ' || quote_ident($1) || '.' || $2 + || ' should extend type ' || $3 + ); +$$ LANGUAGE SQL; + +-- domain_type_is( domain, type, description ) +CREATE OR REPLACE FUNCTION domain_type_is( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + actual_type TEXT := _get_dtype($1); +BEGIN + IF actual_type IS NULL THEN + RETURN fail( $3 ) || E'\n' || diag ( + ' Domain ' || $1 || ' does not exist' + ); + END IF; + + RETURN is( actual_type, _typename($2), $3 ); +END; +$$ LANGUAGE plpgsql; + +-- domain_type_is( domain, type ) +CREATE OR REPLACE FUNCTION domain_type_is( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT domain_type_is( + $1, $2, + 'Domain ' || $1 || ' should extend type ' || $2 + ); +$$ LANGUAGE SQL; + +-- domain_type_isnt( schema, domain, schema, type, description ) +CREATE OR REPLACE FUNCTION domain_type_isnt( NAME, TEXT, NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + actual_type TEXT := _get_dtype($1, $2, true); +BEGIN + IF actual_type IS NULL THEN + RETURN fail( $5 ) || E'\n' || diag ( + ' Domain ' || quote_ident($1) || '.' || $2 + || ' does not exist' + ); + END IF; + + IF quote_ident($3) = ANY(current_schemas(true)) THEN + RETURN isnt( actual_type, quote_ident($3) || '.' || _typename($4), $5); + END IF; + RETURN isnt( actual_type, _typename(quote_ident($3) || '.' || $4), $5); +END; +$$ LANGUAGE plpgsql; + +-- domain_type_isnt( schema, domain, schema, type ) +CREATE OR REPLACE FUNCTION domain_type_isnt( NAME, TEXT, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT domain_type_isnt( + $1, $2, $3, $4, + 'Domain ' || quote_ident($1) || '.' || $2 + || ' should not extend type ' || quote_ident($3) || '.' || $4 + ); +$$ LANGUAGE SQL; + +-- domain_type_isnt( schema, domain, type, description ) +CREATE OR REPLACE FUNCTION domain_type_isnt( NAME, TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + actual_type TEXT := _get_dtype($1, $2, false); +BEGIN + IF actual_type IS NULL THEN + RETURN fail( $4 ) || E'\n' || diag ( + ' Domain ' || quote_ident($1) || '.' || $2 + || ' does not exist' + ); + END IF; + + RETURN isnt( actual_type, _typename($3), $4 ); +END; +$$ LANGUAGE plpgsql; + +-- domain_type_isnt( schema, domain, type ) +CREATE OR REPLACE FUNCTION domain_type_isnt( NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT domain_type_isnt( + $1, $2, $3, + 'Domain ' || quote_ident($1) || '.' || $2 + || ' should not extend type ' || $3 + ); +$$ LANGUAGE SQL; + +-- domain_type_isnt( domain, type, description ) +CREATE OR REPLACE FUNCTION domain_type_isnt( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + actual_type TEXT := _get_dtype($1); +BEGIN + IF actual_type IS NULL THEN + RETURN fail( $3 ) || E'\n' || diag ( + ' Domain ' || $1 || ' does not exist' + ); + END IF; + + RETURN isnt( actual_type, _typename($2), $3 ); +END; +$$ LANGUAGE plpgsql; + +-- domain_type_isnt( domain, type ) +CREATE OR REPLACE FUNCTION domain_type_isnt( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT domain_type_isnt( + $1, $2, + 'Domain ' || $1 || ' should not extend type ' || $2 + ); +$$ LANGUAGE SQL; + +-- row_eq( sql, record, description ) +CREATE OR REPLACE FUNCTION row_eq( TEXT, anyelement, TEXT ) +RETURNS TEXT AS $$ +DECLARE + rec RECORD; +BEGIN + EXECUTE _query($1) INTO rec; + IF NOT rec IS DISTINCT FROM $2 THEN RETURN ok(true, $3); END IF; + RETURN ok(false, $3 ) || E'\n' || diag( + ' have: ' || CASE WHEN rec IS NULL THEN 'NULL' ELSE rec::text END || + E'\n want: ' || CASE WHEN $2 IS NULL THEN 'NULL' ELSE $2::text END + ); +END; +$$ LANGUAGE plpgsql; + +-- row_eq( sql, record ) +CREATE OR REPLACE FUNCTION row_eq( TEXT, anyelement ) +RETURNS TEXT AS $$ + SELECT row_eq($1, $2, NULL ); +$$ LANGUAGE sql; + +-- triggers_are( schema, table, triggers[], description ) +CREATE OR REPLACE FUNCTION triggers_are( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'triggers', + ARRAY( + SELECT t.tgname + FROM pg_catalog.pg_trigger t + JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid + JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = $1 + AND c.relname = $2 + AND NOT t.tgisinternal + EXCEPT + SELECT $3[i] + FROM generate_series(1, array_upper($3, 1)) s(i) + ), + ARRAY( + SELECT $3[i] + FROM generate_series(1, array_upper($3, 1)) s(i) + EXCEPT + SELECT t.tgname + FROM pg_catalog.pg_trigger t + JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid + JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = $1 + AND c.relname = $2 + AND NOT t.tgisinternal + ), + $4 + ); +$$ LANGUAGE SQL; + +-- triggers_are( schema, table, triggers[] ) +CREATE OR REPLACE FUNCTION triggers_are( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT triggers_are( $1, $2, $3, 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct triggers' ); +$$ LANGUAGE SQL; + +-- triggers_are( table, triggers[], description ) +CREATE OR REPLACE FUNCTION triggers_are( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'triggers', + ARRAY( + SELECT t.tgname + FROM pg_catalog.pg_trigger t + JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid + JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname = $1 + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + AND NOT t.tgisinternal + EXCEPT + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + ), + ARRAY( + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + EXCEPT + SELECT t.tgname + FROM pg_catalog.pg_trigger t + JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid + JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + AND NOT t.tgisinternal + ), + $3 + ); +$$ LANGUAGE SQL; + +-- triggers_are( table, triggers[] ) +CREATE OR REPLACE FUNCTION triggers_are( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT triggers_are( $1, $2, 'Table ' || quote_ident($1) || ' should have the correct triggers' ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _areni ( text, text[], text[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + what ALIAS FOR $1; + extras ALIAS FOR $2; + missing ALIAS FOR $3; + descr ALIAS FOR $4; + msg TEXT := ''; + res BOOLEAN := TRUE; +BEGIN + IF extras[1] IS NOT NULL THEN + res = FALSE; + msg := E'\n' || diag( + ' Extra ' || what || E':\n ' + || _array_to_sorted_string( extras, E'\n ' ) + ); + END IF; + IF missing[1] IS NOT NULL THEN + res = FALSE; + msg := msg || E'\n' || diag( + ' Missing ' || what || E':\n ' + || _array_to_sorted_string( missing, E'\n ' ) + ); + END IF; + + RETURN ok(res, descr) || msg; +END; +$$ LANGUAGE plpgsql; + +-- casts_are( casts[], description ) +CREATE OR REPLACE FUNCTION casts_are ( TEXT[], TEXT ) +RETURNS TEXT AS $$ + SELECT _areni( + 'casts', + ARRAY( + SELECT pg_catalog.format_type(castsource, NULL) + || ' AS ' || pg_catalog.format_type(casttarget, NULL) + FROM pg_catalog.pg_cast c + EXCEPT + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + ), + ARRAY( + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + EXCEPT + SELECT pg_catalog.format_type(castsource, NULL) + || ' AS ' || pg_catalog.format_type(casttarget, NULL) + FROM pg_catalog.pg_cast c + ), + $2 + ); +$$ LANGUAGE sql; + +-- casts_are( casts[] ) +CREATE OR REPLACE FUNCTION casts_are ( TEXT[] ) +RETURNS TEXT AS $$ + SELECT casts_are( $1, 'There should be the correct casts'); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) +RETURNS TEXT AS $$ + SELECT $1 || substring($2::regoperator::text, '[(][^)]+[)]$') +$$ LANGUAGE SQL; + +-- operators_are( schema, operators[], description ) +CREATE OR REPLACE FUNCTION operators_are( NAME, TEXT[], TEXT ) +RETURNS TEXT AS $$ + SELECT _areni( + 'operators', + ARRAY( + SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype + FROM pg_catalog.pg_operator o + JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid + WHERE n.nspname = $1 + EXCEPT + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + ), + ARRAY( + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + EXCEPT + SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype + FROM pg_catalog.pg_operator o + JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid + WHERE n.nspname = $1 + ), + $3 + ); +$$ LANGUAGE SQL; + +-- operators_are( schema, operators[] ) +CREATE OR REPLACE FUNCTION operators_are ( NAME, TEXT[] ) +RETURNS TEXT AS $$ + SELECT operators_are($1, $2, 'Schema ' || quote_ident($1) || ' should have the correct operators' ); +$$ LANGUAGE SQL; + +-- operators_are( operators[], description ) +CREATE OR REPLACE FUNCTION operators_are( TEXT[], TEXT ) +RETURNS TEXT AS $$ + SELECT _areni( + 'operators', + ARRAY( + SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype + FROM pg_catalog.pg_operator o + JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid + WHERE pg_catalog.pg_operator_is_visible(o.oid) + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + EXCEPT + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + ), + ARRAY( + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + EXCEPT + SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype + FROM pg_catalog.pg_operator o + JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid + WHERE pg_catalog.pg_operator_is_visible(o.oid) + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + ), + $2 + ); +$$ LANGUAGE SQL; + +-- operators_are( operators[] ) +CREATE OR REPLACE FUNCTION operators_are ( TEXT[] ) +RETURNS TEXT AS $$ + SELECT operators_are($1, 'There should be the correct operators') +$$ LANGUAGE SQL; + +-- columns_are( schema, table, columns[], description ) +CREATE OR REPLACE FUNCTION columns_are( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'columns', + ARRAY( + SELECT a.attname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid + WHERE n.nspname = $1 + AND c.relname = $2 + AND a.attnum > 0 + AND NOT a.attisdropped + EXCEPT + SELECT $3[i] + FROM generate_series(1, array_upper($3, 1)) s(i) + ), + ARRAY( + SELECT $3[i] + FROM generate_series(1, array_upper($3, 1)) s(i) + EXCEPT + SELECT a.attname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid + WHERE n.nspname = $1 + AND c.relname = $2 + AND a.attnum > 0 + AND NOT a.attisdropped + ), + $4 + ); +$$ LANGUAGE SQL; + +-- columns_are( schema, table, columns[] ) +CREATE OR REPLACE FUNCTION columns_are( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT columns_are( $1, $2, $3, 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct columns' ); +$$ LANGUAGE SQL; + +-- columns_are( table, columns[], description ) +CREATE OR REPLACE FUNCTION columns_are( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'columns', + ARRAY( + SELECT a.attname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid + WHERE n.nspname NOT IN ('pg_catalog', 'information_schema') + AND pg_catalog.pg_table_is_visible(c.oid) + AND c.relname = $1 + AND a.attnum > 0 + AND NOT a.attisdropped + EXCEPT + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + ), + ARRAY( + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + EXCEPT + SELECT a.attname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid + WHERE n.nspname NOT IN ('pg_catalog', 'information_schema') + AND pg_catalog.pg_table_is_visible(c.oid) + AND c.relname = $1 + AND a.attnum > 0 + AND NOT a.attisdropped + ), + $3 + ); +$$ LANGUAGE SQL; + +-- columns_are( table, columns[] ) +CREATE OR REPLACE FUNCTION columns_are( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT columns_are( $1, $2, 'Table ' || quote_ident($1) || ' should have the correct columns' ); +$$ LANGUAGE SQL; + +-- _get_db_owner( dbname ) +CREATE OR REPLACE FUNCTION _get_db_owner( NAME ) +RETURNS NAME AS $$ + SELECT pg_catalog.pg_get_userbyid(datdba) + FROM pg_catalog.pg_database + WHERE datname = $1; +$$ LANGUAGE SQL; + +-- db_owner_is ( dbname, user, description ) +CREATE OR REPLACE FUNCTION db_owner_is ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + dbowner NAME := _get_db_owner($1); +BEGIN + -- Make sure the database exists. + IF dbowner IS NULL THEN + RETURN ok(FALSE, $3) || E'\n' || diag( + E' Database ' || quote_ident($1) || ' does not exist' + ); + END IF; + + RETURN is(dbowner, $2, $3); +END; +$$ LANGUAGE plpgsql; + +-- db_owner_is ( dbname, user ) +CREATE OR REPLACE FUNCTION db_owner_is ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT db_owner_is( + $1, $2, + 'Database ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) + ); +$$ LANGUAGE sql; + +-- _get_schema_owner( schema ) +CREATE OR REPLACE FUNCTION _get_schema_owner( NAME ) +RETURNS NAME AS $$ + SELECT pg_catalog.pg_get_userbyid(nspowner) + FROM pg_catalog.pg_namespace + WHERE nspname = $1; +$$ LANGUAGE SQL; + +-- schema_owner_is ( schema, user, description ) +CREATE OR REPLACE FUNCTION schema_owner_is ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_schema_owner($1); +BEGIN + -- Make sure the schema exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $3) || E'\n' || diag( + E' Schema ' || quote_ident($1) || ' does not exist' + ); + END IF; + + RETURN is(owner, $2, $3); +END; +$$ LANGUAGE plpgsql; + +-- schema_owner_is ( schema, user ) +CREATE OR REPLACE FUNCTION schema_owner_is ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT schema_owner_is( + $1, $2, + 'Schema ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _get_rel_owner ( NAME, NAME ) +RETURNS NAME AS $$ + SELECT pg_catalog.pg_get_userbyid(c.relowner) + FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = $1 + AND c.relname = $2 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_rel_owner ( NAME ) +RETURNS NAME AS $$ + SELECT pg_catalog.pg_get_userbyid(c.relowner) + FROM pg_catalog.pg_class c + WHERE c.relname = $1 + AND pg_catalog.pg_table_is_visible(c.oid) +$$ LANGUAGE SQL; + +-- relation_owner_is ( schema, relation, user, description ) +CREATE OR REPLACE FUNCTION relation_owner_is ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_rel_owner($1, $2); +BEGIN + -- Make sure the relation exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + E' Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + END IF; + + RETURN is(owner, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- relation_owner_is ( schema, relation, user ) +CREATE OR REPLACE FUNCTION relation_owner_is ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT relation_owner_is( + $1, $2, $3, + 'Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) + ); +$$ LANGUAGE sql; + +-- relation_owner_is ( relation, user, description ) +CREATE OR REPLACE FUNCTION relation_owner_is ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_rel_owner($1); +BEGIN + -- Make sure the relation exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $3) || E'\n' || diag( + E' Relation ' || quote_ident($1) || ' does not exist' + ); + END IF; + + RETURN is(owner, $2, $3); +END; +$$ LANGUAGE plpgsql; + +-- relation_owner_is ( relation, user ) +CREATE OR REPLACE FUNCTION relation_owner_is ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT relation_owner_is( + $1, $2, + 'Relation ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _get_rel_owner ( CHAR[], NAME, NAME ) +RETURNS NAME AS $$ + SELECT pg_catalog.pg_get_userbyid(c.relowner) + FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind = ANY($1) + AND n.nspname = $2 + AND c.relname = $3 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_rel_owner ( CHAR[], NAME ) +RETURNS NAME AS $$ + SELECT pg_catalog.pg_get_userbyid(c.relowner) + FROM pg_catalog.pg_class c + WHERE c.relkind = ANY($1) + AND c.relname = $2 + AND pg_catalog.pg_table_is_visible(c.oid) +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_rel_owner ( CHAR, NAME, NAME ) +RETURNS NAME AS $$ + SELECT _get_rel_owner(ARRAY[$1], $2, $3); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_rel_owner ( CHAR, NAME ) +RETURNS NAME AS $$ + SELECT _get_rel_owner(ARRAY[$1], $2); +$$ LANGUAGE SQL; + +-- table_owner_is ( schema, table, user, description ) +CREATE OR REPLACE FUNCTION table_owner_is ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_rel_owner('{r,p}'::char[], $1, $2); +BEGIN + -- Make sure the table exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + E' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + END IF; + + RETURN is(owner, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- table_owner_is ( schema, table, user ) +CREATE OR REPLACE FUNCTION table_owner_is ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT table_owner_is( + $1, $2, $3, + 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) + ); +$$ LANGUAGE sql; + +-- table_owner_is ( table, user, description ) +CREATE OR REPLACE FUNCTION table_owner_is ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_rel_owner('{r,p}'::char[], $1); +BEGIN + -- Make sure the table exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $3) || E'\n' || diag( + E' Table ' || quote_ident($1) || ' does not exist' + ); + END IF; + + RETURN is(owner, $2, $3); +END; +$$ LANGUAGE plpgsql; + +-- table_owner_is ( table, user ) +CREATE OR REPLACE FUNCTION table_owner_is ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT table_owner_is( + $1, $2, + 'Table ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) + ); +$$ LANGUAGE sql; + +-- view_owner_is ( schema, view, user, description ) +CREATE OR REPLACE FUNCTION view_owner_is ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_rel_owner('v'::char, $1, $2); +BEGIN + -- Make sure the view exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + E' View ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + END IF; + + RETURN is(owner, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- view_owner_is ( schema, view, user ) +CREATE OR REPLACE FUNCTION view_owner_is ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT view_owner_is( + $1, $2, $3, + 'View ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) + ); +$$ LANGUAGE sql; + +-- view_owner_is ( view, user, description ) +CREATE OR REPLACE FUNCTION view_owner_is ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_rel_owner('v'::char, $1); +BEGIN + -- Make sure the view exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $3) || E'\n' || diag( + E' View ' || quote_ident($1) || ' does not exist' + ); + END IF; + + RETURN is(owner, $2, $3); +END; +$$ LANGUAGE plpgsql; + +-- view_owner_is ( view, user ) +CREATE OR REPLACE FUNCTION view_owner_is ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT view_owner_is( + $1, $2, + 'View ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) + ); +$$ LANGUAGE sql; + +-- sequence_owner_is ( schema, sequence, user, description ) +CREATE OR REPLACE FUNCTION sequence_owner_is ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_rel_owner('S'::char, $1, $2); +BEGIN + -- Make sure the sequence exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + E' Sequence ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + END IF; + + RETURN is(owner, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- sequence_owner_is ( schema, sequence, user ) +CREATE OR REPLACE FUNCTION sequence_owner_is ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT sequence_owner_is( + $1, $2, $3, + 'Sequence ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) + ); +$$ LANGUAGE sql; + +-- sequence_owner_is ( sequence, user, description ) +CREATE OR REPLACE FUNCTION sequence_owner_is ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_rel_owner('S'::char, $1); +BEGIN + -- Make sure the sequence exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $3) || E'\n' || diag( + E' Sequence ' || quote_ident($1) || ' does not exist' + ); + END IF; + + RETURN is(owner, $2, $3); +END; +$$ LANGUAGE plpgsql; + +-- sequence_owner_is ( sequence, user ) +CREATE OR REPLACE FUNCTION sequence_owner_is ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT sequence_owner_is( + $1, $2, + 'Sequence ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) + ); +$$ LANGUAGE sql; + +-- composite_owner_is ( schema, composite, user, description ) +CREATE OR REPLACE FUNCTION composite_owner_is ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_rel_owner('c'::char, $1, $2); +BEGIN + -- Make sure the composite exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + E' Composite type ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + END IF; + + RETURN is(owner, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- composite_owner_is ( schema, composite, user ) +CREATE OR REPLACE FUNCTION composite_owner_is ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT composite_owner_is( + $1, $2, $3, + 'Composite type ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) + ); +$$ LANGUAGE sql; + +-- composite_owner_is ( composite, user, description ) +CREATE OR REPLACE FUNCTION composite_owner_is ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_rel_owner('c'::char, $1); +BEGIN + -- Make sure the composite exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $3) || E'\n' || diag( + E' Composite type ' || quote_ident($1) || ' does not exist' + ); + END IF; + + RETURN is(owner, $2, $3); +END; +$$ LANGUAGE plpgsql; + +-- composite_owner_is ( composite, user ) +CREATE OR REPLACE FUNCTION composite_owner_is ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT composite_owner_is( + $1, $2, + 'Composite type ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) + ); +$$ LANGUAGE sql; + +-- foreign_table_owner_is ( schema, table, user, description ) +CREATE OR REPLACE FUNCTION foreign_table_owner_is ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_rel_owner('f'::char, $1, $2); +BEGIN + -- Make sure the table exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + E' Foreign table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + END IF; + + RETURN is(owner, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- foreign_table_owner_is ( schema, table, user ) +CREATE OR REPLACE FUNCTION foreign_table_owner_is ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT foreign_table_owner_is( + $1, $2, $3, + 'Foreign table ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) + ); +$$ LANGUAGE sql; + +-- foreign_table_owner_is ( table, user, description ) +CREATE OR REPLACE FUNCTION foreign_table_owner_is ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_rel_owner('f'::char, $1); +BEGIN + -- Make sure the table exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $3) || E'\n' || diag( + E' Foreign table ' || quote_ident($1) || ' does not exist' + ); + END IF; + + RETURN is(owner, $2, $3); +END; +$$ LANGUAGE plpgsql; + +-- foreign_table_owner_is ( table, user ) +CREATE OR REPLACE FUNCTION foreign_table_owner_is ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT foreign_table_owner_is( + $1, $2, + 'Foreign table ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _get_func_owner ( NAME, NAME, NAME[] ) +RETURNS NAME AS $$ + SELECT owner + FROM tap_funky + WHERE schema = $1 + AND name = $2 + AND args = _funkargs($3) +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_func_owner ( NAME, NAME[] ) +RETURNS NAME AS $$ + SELECT owner + FROM tap_funky + WHERE name = $1 + AND args = _funkargs($2) + AND is_visible +$$ LANGUAGE SQL; + +-- function_owner_is( schema, function, args[], user, description ) +CREATE OR REPLACE FUNCTION function_owner_is ( NAME, NAME, NAME[], NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_func_owner($1, $2, $3); +BEGIN + -- Make sure the function exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $5) || E'\n' || diag( + E' Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') does not exist' + ); + END IF; + + RETURN is(owner, $4, $5); +END; +$$ LANGUAGE plpgsql; + +-- function_owner_is( schema, function, args[], user ) +CREATE OR REPLACE FUNCTION function_owner_is( NAME, NAME, NAME[], NAME ) +RETURNS TEXT AS $$ + SELECT function_owner_is( + $1, $2, $3, $4, + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should be owned by ' || quote_ident($4) + ); +$$ LANGUAGE sql; + +-- function_owner_is( function, args[], user, description ) +CREATE OR REPLACE FUNCTION function_owner_is ( NAME, NAME[], NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_func_owner($1, $2); +BEGIN + -- Make sure the function exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + E' Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') does not exist' + ); + END IF; + + RETURN is(owner, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- function_owner_is( function, args[], user ) +CREATE OR REPLACE FUNCTION function_owner_is( NAME, NAME[], NAME ) +RETURNS TEXT AS $$ + SELECT function_owner_is( + $1, $2, $3, + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should be owned by ' || quote_ident($3) + ); +$$ LANGUAGE sql; + +-- _get_tablespace_owner( tablespace ) +CREATE OR REPLACE FUNCTION _get_tablespace_owner( NAME ) +RETURNS NAME AS $$ + SELECT pg_catalog.pg_get_userbyid(spcowner) + FROM pg_catalog.pg_tablespace + WHERE spcname = $1; +$$ LANGUAGE SQL; + +-- tablespace_owner_is ( tablespace, user, description ) +CREATE OR REPLACE FUNCTION tablespace_owner_is ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_tablespace_owner($1); +BEGIN + -- Make sure the tablespace exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $3) || E'\n' || diag( + E' Tablespace ' || quote_ident($1) || ' does not exist' + ); + END IF; + + RETURN is(owner, $2, $3); +END; +$$ LANGUAGE plpgsql; + +-- tablespace_owner_is ( tablespace, user ) +CREATE OR REPLACE FUNCTION tablespace_owner_is ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT tablespace_owner_is( + $1, $2, + 'Tablespace ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _get_index_owner( NAME, NAME, NAME ) +RETURNS NAME AS $$ + SELECT pg_catalog.pg_get_userbyid(ci.relowner) + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace + WHERE n.nspname = $1 + AND ct.relname = $2 + AND ci.relname = $3; +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _get_index_owner( NAME, NAME ) +RETURNS NAME AS $$ + SELECT pg_catalog.pg_get_userbyid(ci.relowner) + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid + WHERE ct.relname = $1 + AND ci.relname = $2 + AND pg_catalog.pg_table_is_visible(ct.oid); +$$ LANGUAGE sql; + +-- index_owner_is ( schema, table, index, user, description ) +CREATE OR REPLACE FUNCTION index_owner_is ( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_index_owner($1, $2, $3); +BEGIN + -- Make sure the index exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $5) || E'\n' || diag( + E' Index ' || quote_ident($3) || ' ON ' + || quote_ident($1) || '.' || quote_ident($2) || ' not found' + ); + END IF; + + RETURN is(owner, $4, $5); +END; +$$ LANGUAGE plpgsql; + +-- index_owner_is ( schema, table, index, user ) +CREATE OR REPLACE FUNCTION index_owner_is ( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT index_owner_is( + $1, $2, $3, $4, + 'Index ' || quote_ident($3) || ' ON ' + || quote_ident($1) || '.' || quote_ident($2) + || ' should be owned by ' || quote_ident($4) + ); +$$ LANGUAGE sql; + +-- index_owner_is ( table, index, user, description ) +CREATE OR REPLACE FUNCTION index_owner_is ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_index_owner($1, $2); +BEGIN + -- Make sure the index exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + E' Index ' || quote_ident($2) || ' ON ' || quote_ident($1) || ' not found' + ); + END IF; + + RETURN is(owner, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- index_owner_is ( table, index, user ) +CREATE OR REPLACE FUNCTION index_owner_is ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT index_owner_is( + $1, $2, $3, + 'Index ' || quote_ident($2) || ' ON ' + || quote_ident($1) || ' should be owned by ' || quote_ident($3) + ); +$$ LANGUAGE sql; + +-- _get_language_owner( language ) +CREATE OR REPLACE FUNCTION _get_language_owner( NAME ) +RETURNS NAME AS $$ + SELECT pg_catalog.pg_get_userbyid(lanowner) + FROM pg_catalog.pg_language + WHERE lanname = $1; +$$ LANGUAGE SQL; + +-- language_owner_is ( language, user, description ) +CREATE OR REPLACE FUNCTION language_owner_is ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_language_owner($1); +BEGIN + -- Make sure the language exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $3) || E'\n' || diag( + E' Language ' || quote_ident($1) || ' does not exist' + ); + END IF; + + RETURN is(owner, $2, $3); +END; +$$ LANGUAGE plpgsql; + +-- language_owner_is ( language, user ) +CREATE OR REPLACE FUNCTION language_owner_is ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT language_owner_is( + $1, $2, + 'Language ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _get_opclass_owner ( NAME, NAME ) +RETURNS NAME AS $$ + SELECT pg_catalog.pg_get_userbyid(opcowner) + FROM pg_catalog.pg_opclass oc + JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid + WHERE n.nspname = $1 + AND opcname = $2; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_opclass_owner ( NAME ) +RETURNS NAME AS $$ + SELECT pg_catalog.pg_get_userbyid(opcowner) + FROM pg_catalog.pg_opclass + WHERE opcname = $1 + AND pg_catalog.pg_opclass_is_visible(oid); +$$ LANGUAGE SQL; + +-- opclass_owner_is( schema, opclass, user, description ) +CREATE OR REPLACE FUNCTION opclass_owner_is ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_opclass_owner($1, $2); +BEGIN + -- Make sure the opclass exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + E' Operator class ' || quote_ident($1) || '.' || quote_ident($2) + || ' not found' + ); + END IF; + + RETURN is(owner, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- opclass_owner_is( schema, opclass, user ) +CREATE OR REPLACE FUNCTION opclass_owner_is( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT opclass_owner_is( + $1, $2, $3, + 'Operator class ' || quote_ident($1) || '.' || quote_ident($2) || + ' should be owned by ' || quote_ident($3) + ); +$$ LANGUAGE sql; + +-- opclass_owner_is( opclass, user, description ) +CREATE OR REPLACE FUNCTION opclass_owner_is ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_opclass_owner($1); +BEGIN + -- Make sure the opclass exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $3) || E'\n' || diag( + E' Operator class ' || quote_ident($1) || ' not found' + ); + END IF; + + RETURN is(owner, $2, $3); +END; +$$ LANGUAGE plpgsql; + +-- opclass_owner_is( opclass, user ) +CREATE OR REPLACE FUNCTION opclass_owner_is( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT opclass_owner_is( + $1, $2, + 'Operator class ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _get_type_owner ( NAME, NAME ) +RETURNS NAME AS $$ + SELECT pg_catalog.pg_get_userbyid(t.typowner) + FROM pg_catalog.pg_type t + JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace + WHERE n.nspname = $1 + AND t.typname = $2 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_type_owner ( NAME ) +RETURNS NAME AS $$ + SELECT pg_catalog.pg_get_userbyid(typowner) + FROM pg_catalog.pg_type + WHERE typname = $1 + AND pg_catalog.pg_type_is_visible(oid) +$$ LANGUAGE SQL; + +-- type_owner_is ( schema, type, user, description ) +CREATE OR REPLACE FUNCTION type_owner_is ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_type_owner($1, $2); +BEGIN + -- Make sure the type exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + E' Type ' || quote_ident($1) || '.' || quote_ident($2) || ' not found' + ); + END IF; + + RETURN is(owner, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- type_owner_is ( schema, type, user ) +CREATE OR REPLACE FUNCTION type_owner_is ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT type_owner_is( + $1, $2, $3, + 'Type ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) + ); +$$ LANGUAGE sql; + +-- type_owner_is ( type, user, description ) +CREATE OR REPLACE FUNCTION type_owner_is ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_type_owner($1); +BEGIN + -- Make sure the type exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $3) || E'\n' || diag( + E' Type ' || quote_ident($1) || ' not found' + ); + END IF; + + RETURN is(owner, $2, $3); +END; +$$ LANGUAGE plpgsql; + +-- type_owner_is ( type, user ) +CREATE OR REPLACE FUNCTION type_owner_is ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT type_owner_is( + $1, $2, + 'Type ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _assets_are ( text, text[], text[], TEXT ) +RETURNS TEXT AS $$ + SELECT _areni( + $1, + ARRAY( + SELECT UPPER($2[i]) AS thing + FROM generate_series(1, array_upper($2, 1)) s(i) + EXCEPT + SELECT $3[i] + FROM generate_series(1, array_upper($3, 1)) s(i) + ORDER BY thing + ), + ARRAY( + SELECT $3[i] AS thing + FROM generate_series(1, array_upper($3, 1)) s(i) + EXCEPT + SELECT UPPER($2[i]) + FROM generate_series(1, array_upper($2, 1)) s(i) + ORDER BY thing + ), + $4 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_table_privs(NAME, TEXT) +RETURNS TEXT[] AS $$ +DECLARE + privs TEXT[] := _table_privs(); + grants TEXT[] := '{}'; +BEGIN + FOR i IN 1..array_upper(privs, 1) LOOP + BEGIN + IF pg_catalog.has_table_privilege($1, $2, privs[i]) THEN + grants := grants || privs[i]; + END IF; + EXCEPTION WHEN undefined_table THEN + -- Not a valid table name. + RETURN '{undefined_table}'; + WHEN undefined_object THEN + -- Not a valid role. + RETURN '{undefined_role}'; + WHEN invalid_parameter_value THEN + -- Not a valid permission on this version of PostgreSQL; ignore; + END; + END LOOP; + RETURN grants; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _table_privs() +RETURNS NAME[] AS $$ +DECLARE + pgversion INTEGER := pg_version_num(); +BEGIN + IF pgversion < 80200 THEN RETURN ARRAY[ + 'DELETE', 'INSERT', 'REFERENCES', 'RULE', 'SELECT', 'TRIGGER', 'UPDATE' + ]; + ELSIF pgversion < 80400 THEN RETURN ARRAY[ + 'DELETE', 'INSERT', 'REFERENCES', 'SELECT', 'TRIGGER', 'UPDATE' + ]; + ELSE RETURN ARRAY[ + 'DELETE', 'INSERT', 'REFERENCES', 'SELECT', 'TRIGGER', 'TRUNCATE', 'UPDATE' + ]; + END IF; +END; +$$ language plpgsql; + +-- table_privs_are ( schema, table, user, privileges[], description ) +CREATE OR REPLACE FUNCTION table_privs_are ( NAME, NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_table_privs( $3, quote_ident($1) || '.' || quote_ident($2) ); +BEGIN + IF grants[1] = 'undefined_table' THEN + RETURN ok(FALSE, $5) || E'\n' || diag( + ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_role' THEN + RETURN ok(FALSE, $5) || E'\n' || diag( + ' Role ' || quote_ident($3) || ' does not exist' + ); + END IF; + RETURN _assets_are('privileges', grants, $4, $5); +END; +$$ LANGUAGE plpgsql; + +-- table_privs_are ( schema, table, user, privileges[] ) +CREATE OR REPLACE FUNCTION table_privs_are ( NAME, NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT table_privs_are( + $1, $2, $3, $4, + 'Role ' || quote_ident($3) || ' should be granted ' + || CASE WHEN $4[1] IS NULL THEN 'no privileges' ELSE array_to_string($4, ', ') END + || ' on table ' || quote_ident($1) || '.' || quote_ident($2) + ); +$$ LANGUAGE SQL; + +-- table_privs_are ( table, user, privileges[], description ) +CREATE OR REPLACE FUNCTION table_privs_are ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_table_privs( $2, quote_ident($1) ); +BEGIN + IF grants[1] = 'undefined_table' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_role' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Role ' || quote_ident($2) || ' does not exist' + ); + END IF; + RETURN _assets_are('privileges', grants, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- table_privs_are ( table, user, privileges[] ) +CREATE OR REPLACE FUNCTION table_privs_are ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT table_privs_are( + $1, $2, $3, + 'Role ' || quote_ident($2) || ' should be granted ' + || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END + || ' on table ' || quote_ident($1) + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _db_privs() +RETURNS NAME[] AS $$ +DECLARE + pgversion INTEGER := pg_version_num(); +BEGIN + IF pgversion < 80200 THEN + RETURN ARRAY['CREATE', 'TEMPORARY']; + ELSE + RETURN ARRAY['CREATE', 'CONNECT', 'TEMPORARY']; + END IF; +END; +$$ language plpgsql; + +CREATE OR REPLACE FUNCTION _get_db_privs(NAME, TEXT) +RETURNS TEXT[] AS $$ +DECLARE + privs TEXT[] := _db_privs(); + grants TEXT[] := '{}'; +BEGIN + FOR i IN 1..array_upper(privs, 1) LOOP + BEGIN + IF pg_catalog.has_database_privilege($1, $2, privs[i]) THEN + grants := grants || privs[i]; + END IF; + EXCEPTION WHEN invalid_catalog_name THEN + -- Not a valid db name. + RETURN '{invalid_catalog_name}'; + WHEN undefined_object THEN + -- Not a valid role. + RETURN '{undefined_role}'; + WHEN invalid_parameter_value THEN + -- Not a valid permission on this version of PostgreSQL; ignore; + END; + END LOOP; + RETURN grants; +END; +$$ LANGUAGE plpgsql; + +-- database_privs_are ( db, user, privileges[], description ) +CREATE OR REPLACE FUNCTION database_privs_are ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_db_privs( $2, $1::TEXT ); +BEGIN + IF grants[1] = 'invalid_catalog_name' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Database ' || quote_ident($1) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_role' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Role ' || quote_ident($2) || ' does not exist' + ); + END IF; + RETURN _assets_are('privileges', grants, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- database_privs_are ( db, user, privileges[] ) +CREATE OR REPLACE FUNCTION database_privs_are ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT database_privs_are( + $1, $2, $3, + 'Role ' || quote_ident($2) || ' should be granted ' + || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END + || ' on database ' || quote_ident($1) + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_func_privs(TEXT, TEXT) +RETURNS TEXT[] AS $$ +BEGIN + IF pg_catalog.has_function_privilege($1, $2, 'EXECUTE') THEN + RETURN '{EXECUTE}'; + ELSE + RETURN '{}'; + END IF; +EXCEPTION + -- Not a valid func name. + WHEN undefined_function THEN RETURN '{undefined_function}'; + -- Not a valid role. + WHEN undefined_object THEN RETURN '{undefined_role}'; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _fprivs_are ( TEXT, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_func_privs($2, $1); +BEGIN + IF grants[1] = 'undefined_function' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Function ' || $1 || ' does not exist' + ); + ELSIF grants[1] = 'undefined_role' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Role ' || quote_ident($2) || ' does not exist' + ); + END IF; + RETURN _assets_are('privileges', grants, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- function_privs_are ( schema, function, args[], user, privileges[], description ) +CREATE OR REPLACE FUNCTION function_privs_are ( NAME, NAME, NAME[], NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _fprivs_are( + quote_ident($1) || '.' || quote_ident($2) || '(' || array_to_string($3, ', ') || ')', + $4, $5, $6 + ); +$$ LANGUAGE SQL; + +-- function_privs_are ( schema, function, args[], user, privileges[] ) +CREATE OR REPLACE FUNCTION function_privs_are ( NAME, NAME, NAME[], NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT function_privs_are( + $1, $2, $3, $4, $5, + 'Role ' || quote_ident($4) || ' should be granted ' + || CASE WHEN $5[1] IS NULL THEN 'no privileges' ELSE array_to_string($5, ', ') END + || ' on function ' || quote_ident($1) || '.' || quote_ident($2) + || '(' || array_to_string($3, ', ') || ')' + ); +$$ LANGUAGE SQL; + +-- function_privs_are ( function, args[], user, privileges[], description ) +CREATE OR REPLACE FUNCTION function_privs_are ( NAME, NAME[], NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _fprivs_are( + quote_ident($1) || '(' || array_to_string($2, ', ') || ')', + $3, $4, $5 + ); +$$ LANGUAGE SQL; + +-- function_privs_are ( function, args[], user, privileges[] ) +CREATE OR REPLACE FUNCTION function_privs_are ( NAME, NAME[], NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT function_privs_are( + $1, $2, $3, $4, + 'Role ' || quote_ident($3) || ' should be granted ' + || CASE WHEN $4[1] IS NULL THEN 'no privileges' ELSE array_to_string($4, ', ') END + || ' on function ' || quote_ident($1) || '(' || array_to_string($2, ', ') || ')' + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_lang_privs (NAME, TEXT) +RETURNS TEXT[] AS $$ +BEGIN + IF pg_catalog.has_language_privilege($1, $2, 'USAGE') THEN + RETURN '{USAGE}'; + ELSE + RETURN '{}'; + END IF; +EXCEPTION WHEN undefined_object THEN + -- Same error code for unknown user or language. So figure out which. + RETURN CASE WHEN SQLERRM LIKE '%' || $1 || '%' THEN + '{undefined_role}' + ELSE + '{undefined_language}' + END; +END; +$$ LANGUAGE plpgsql; + +-- language_privs_are ( lang, user, privileges[], description ) +CREATE OR REPLACE FUNCTION language_privs_are ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_lang_privs( $2, quote_ident($1) ); +BEGIN + IF grants[1] = 'undefined_language' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Language ' || quote_ident($1) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_role' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Role ' || quote_ident($2) || ' does not exist' + ); + END IF; + RETURN _assets_are('privileges', grants, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- language_privs_are ( lang, user, privileges[] ) +CREATE OR REPLACE FUNCTION language_privs_are ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT language_privs_are( + $1, $2, $3, + 'Role ' || quote_ident($2) || ' should be granted ' + || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END + || ' on language ' || quote_ident($1) + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_schema_privs(NAME, TEXT) +RETURNS TEXT[] AS $$ +DECLARE + privs TEXT[] := ARRAY['CREATE', 'USAGE']; + grants TEXT[] := '{}'; +BEGIN + FOR i IN 1..array_upper(privs, 1) LOOP + IF pg_catalog.has_schema_privilege($1, $2, privs[i]) THEN + grants := grants || privs[i]; + END IF; + END LOOP; + RETURN grants; +EXCEPTION + -- Not a valid schema name. + WHEN invalid_schema_name THEN RETURN '{invalid_schema_name}'; + -- Not a valid role. + WHEN undefined_object THEN RETURN '{undefined_role}'; +END; +$$ LANGUAGE plpgsql; + +-- schema_privs_are ( schema, user, privileges[], description ) +CREATE OR REPLACE FUNCTION schema_privs_are ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_schema_privs( $2, $1::TEXT ); +BEGIN + IF grants[1] = 'invalid_schema_name' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Schema ' || quote_ident($1) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_role' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Role ' || quote_ident($2) || ' does not exist' + ); + END IF; + RETURN _assets_are('privileges', grants, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- schema_privs_are ( schema, user, privileges[] ) +CREATE OR REPLACE FUNCTION schema_privs_are ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT schema_privs_are( + $1, $2, $3, + 'Role ' || quote_ident($2) || ' should be granted ' + || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END + || ' on schema ' || quote_ident($1) + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_tablespaceprivs (NAME, TEXT) +RETURNS TEXT[] AS $$ +BEGIN + IF pg_catalog.has_tablespace_privilege($1, $2, 'CREATE') THEN + RETURN '{CREATE}'; + ELSE + RETURN '{}'; + END IF; +EXCEPTION WHEN undefined_object THEN + -- Same error code for unknown user or tablespace. So figure out which. + RETURN CASE WHEN SQLERRM LIKE '%' || $1 || '%' THEN + '{undefined_role}' + ELSE + '{undefined_tablespace}' + END; +END; +$$ LANGUAGE plpgsql; + +-- tablespace_privs_are ( tablespace, user, privileges[], description ) +CREATE OR REPLACE FUNCTION tablespace_privs_are ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_tablespaceprivs( $2, $1::TEXT ); +BEGIN + IF grants[1] = 'undefined_tablespace' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Tablespace ' || quote_ident($1) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_role' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Role ' || quote_ident($2) || ' does not exist' + ); + END IF; + RETURN _assets_are('privileges', grants, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- tablespace_privs_are ( tablespace, user, privileges[] ) +CREATE OR REPLACE FUNCTION tablespace_privs_are ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT tablespace_privs_are( + $1, $2, $3, + 'Role ' || quote_ident($2) || ' should be granted ' + || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END + || ' on tablespace ' || quote_ident($1) + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_sequence_privs(NAME, TEXT) +RETURNS TEXT[] AS $$ +DECLARE + privs TEXT[] := ARRAY['SELECT', 'UPDATE', 'USAGE']; + grants TEXT[] := '{}'; +BEGIN + FOR i IN 1..array_upper(privs, 1) LOOP + BEGIN + IF pg_catalog.has_sequence_privilege($1, $2, privs[i]) THEN + grants := grants || privs[i]; + END IF; + EXCEPTION WHEN undefined_table THEN + -- Not a valid sequence name. + RETURN '{undefined_table}'; + WHEN undefined_object THEN + -- Not a valid role. + RETURN '{undefined_role}'; + WHEN invalid_parameter_value THEN + -- Not a valid permission on this version of PostgreSQL; ignore; + END; + END LOOP; + RETURN grants; +END; +$$ LANGUAGE plpgsql; + +-- sequence_privs_are ( schema, sequence, user, privileges[], description ) +CREATE OR REPLACE FUNCTION sequence_privs_are ( NAME, NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_sequence_privs( $3, quote_ident($1) || '.' || quote_ident($2) ); +BEGIN + IF grants[1] = 'undefined_table' THEN + RETURN ok(FALSE, $5) || E'\n' || diag( + ' Sequence ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_role' THEN + RETURN ok(FALSE, $5) || E'\n' || diag( + ' Role ' || quote_ident($3) || ' does not exist' + ); + END IF; + RETURN _assets_are('privileges', grants, $4, $5); +END; +$$ LANGUAGE plpgsql; + +-- sequence_privs_are ( schema, sequence, user, privileges[] ) +CREATE OR REPLACE FUNCTION sequence_privs_are ( NAME, NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT sequence_privs_are( + $1, $2, $3, $4, + 'Role ' || quote_ident($3) || ' should be granted ' + || CASE WHEN $4[1] IS NULL THEN 'no privileges' ELSE array_to_string($4, ', ') END + || ' on sequence '|| quote_ident($1) || '.' || quote_ident($2) + ); +$$ LANGUAGE SQL; + +-- sequence_privs_are ( sequence, user, privileges[], description ) +CREATE OR REPLACE FUNCTION sequence_privs_are ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_sequence_privs( $2, quote_ident($1) ); +BEGIN + IF grants[1] = 'undefined_table' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Sequence ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_role' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Role ' || quote_ident($2) || ' does not exist' + ); + END IF; + RETURN _assets_are('privileges', grants, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- sequence_privs_are ( sequence, user, privileges[] ) +CREATE OR REPLACE FUNCTION sequence_privs_are ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT sequence_privs_are( + $1, $2, $3, + 'Role ' || quote_ident($2) || ' should be granted ' + || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END + || ' on sequence ' || quote_ident($1) + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_ac_privs(NAME, TEXT) +RETURNS TEXT[] AS $$ +DECLARE + privs TEXT[] := ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE']; + grants TEXT[] := '{}'; +BEGIN + FOR i IN 1..array_upper(privs, 1) LOOP + BEGIN + IF pg_catalog.has_any_column_privilege($1, $2, privs[i]) THEN + grants := grants || privs[i]; + END IF; + EXCEPTION WHEN undefined_table THEN + -- Not a valid table name. + RETURN '{undefined_table}'; + WHEN undefined_object THEN + -- Not a valid role. + RETURN '{undefined_role}'; + WHEN invalid_parameter_value THEN + -- Not a valid permission on this version of PostgreSQL; ignore; + END; + END LOOP; + RETURN grants; +END; +$$ LANGUAGE plpgsql; + +-- any_column_privs_are ( schema, table, user, privileges[], description ) +CREATE OR REPLACE FUNCTION any_column_privs_are ( NAME, NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_ac_privs( $3, quote_ident($1) || '.' || quote_ident($2) ); +BEGIN + IF grants[1] = 'undefined_table' THEN + RETURN ok(FALSE, $5) || E'\n' || diag( + ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_role' THEN + RETURN ok(FALSE, $5) || E'\n' || diag( + ' Role ' || quote_ident($3) || ' does not exist' + ); + END IF; + RETURN _assets_are('privileges', grants, $4, $5); +END; +$$ LANGUAGE plpgsql; + +-- any_column_privs_are ( schema, table, user, privileges[] ) +CREATE OR REPLACE FUNCTION any_column_privs_are ( NAME, NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT any_column_privs_are( + $1, $2, $3, $4, + 'Role ' || quote_ident($3) || ' should be granted ' + || CASE WHEN $4[1] IS NULL THEN 'no privileges' ELSE array_to_string($4, ', ') END + || ' on any column in '|| quote_ident($1) || '.' || quote_ident($2) + ); +$$ LANGUAGE SQL; + +-- any_column_privs_are ( table, user, privileges[], description ) +CREATE OR REPLACE FUNCTION any_column_privs_are ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_ac_privs( $2, quote_ident($1) ); +BEGIN + IF grants[1] = 'undefined_table' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_role' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Role ' || quote_ident($2) || ' does not exist' + ); + END IF; + RETURN _assets_are('privileges', grants, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- any_column_privs_are ( table, user, privileges[] ) +CREATE OR REPLACE FUNCTION any_column_privs_are ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT any_column_privs_are( + $1, $2, $3, + 'Role ' || quote_ident($2) || ' should be granted ' + || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END + || ' on any column in ' || quote_ident($1) + ); +$$ LANGUAGE SQL; + +-- _get_col_privs(user, table, column) +CREATE OR REPLACE FUNCTION _get_col_privs(NAME, TEXT, NAME) +RETURNS TEXT[] AS $$ +DECLARE + privs TEXT[] := ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE']; + grants TEXT[] := '{}'; +BEGIN + FOR i IN 1..array_upper(privs, 1) LOOP + IF pg_catalog.has_column_privilege($1, $2, $3, privs[i]) THEN + grants := grants || privs[i]; + END IF; + END LOOP; + RETURN grants; +EXCEPTION + -- Not a valid column name. + WHEN undefined_column THEN RETURN '{undefined_column}'; + -- Not a valid table name. + WHEN undefined_table THEN RETURN '{undefined_table}'; + -- Not a valid role. + WHEN undefined_object THEN RETURN '{undefined_role}'; +END; +$$ LANGUAGE plpgsql; + +-- column_privs_are ( schema, table, column, user, privileges[], description ) +CREATE OR REPLACE FUNCTION column_privs_are ( NAME, NAME, NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_col_privs( $4, quote_ident($1) || '.' || quote_ident($2), $3 ); +BEGIN + IF grants[1] = 'undefined_column' THEN + RETURN ok(FALSE, $6) || E'\n' || diag( + ' Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) + || ' does not exist' + ); + ELSIF grants[1] = 'undefined_table' THEN + RETURN ok(FALSE, $6) || E'\n' || diag( + ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_role' THEN + RETURN ok(FALSE, $6) || E'\n' || diag( + ' Role ' || quote_ident($4) || ' does not exist' + ); + END IF; + RETURN _assets_are('privileges', grants, $5, $6); +END; +$$ LANGUAGE plpgsql; + +-- column_privs_are ( schema, table, column, user, privileges[] ) +CREATE OR REPLACE FUNCTION column_privs_are ( NAME, NAME, NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT column_privs_are( + $1, $2, $3, $4, $5, + 'Role ' || quote_ident($4) || ' should be granted ' + || CASE WHEN $5[1] IS NULL THEN 'no privileges' ELSE array_to_string($5, ', ') END + || ' on column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) + ); +$$ LANGUAGE SQL; + +-- column_privs_are ( table, column, user, privileges[], description ) +CREATE OR REPLACE FUNCTION column_privs_are ( NAME, NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_col_privs( $3, quote_ident($1), $2 ); +BEGIN + IF grants[1] = 'undefined_column' THEN + RETURN ok(FALSE, $5) || E'\n' || diag( + ' Column ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_table' THEN + RETURN ok(FALSE, $5) || E'\n' || diag( + ' Table ' || quote_ident($1) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_role' THEN + RETURN ok(FALSE, $5) || E'\n' || diag( + ' Role ' || quote_ident($3) || ' does not exist' + ); + END IF; + RETURN _assets_are('privileges', grants, $4, $5); +END; +$$ LANGUAGE plpgsql; + +-- column_privs_are ( table, column, user, privileges[] ) +CREATE OR REPLACE FUNCTION column_privs_are ( NAME, NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT column_privs_are( + $1, $2, $3, $4, + 'Role ' || quote_ident($3) || ' should be granted ' + || CASE WHEN $4[1] IS NULL THEN 'no privileges' ELSE array_to_string($4, ', ') END + || ' on column ' || quote_ident($1) || '.' || quote_ident($2) + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_fdw_privs (NAME, TEXT) +RETURNS TEXT[] AS $$ +BEGIN + IF pg_catalog.has_foreign_data_wrapper_privilege($1, $2, 'USAGE') THEN + RETURN '{USAGE}'; + ELSE + RETURN '{}'; + END IF; +EXCEPTION WHEN undefined_object THEN + -- Same error code for unknown user or fdw. So figure out which. + RETURN CASE WHEN SQLERRM LIKE '%' || $1 || '%' THEN + '{undefined_role}' + ELSE + '{undefined_fdw}' + END; +END; +$$ LANGUAGE plpgsql; + +-- fdw_privs_are ( fdw, user, privileges[], description ) +CREATE OR REPLACE FUNCTION fdw_privs_are ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_fdw_privs( $2, $1::TEXT ); +BEGIN + IF grants[1] = 'undefined_fdw' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' FDW ' || quote_ident($1) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_role' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Role ' || quote_ident($2) || ' does not exist' + ); + END IF; + RETURN _assets_are('privileges', grants, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- fdw_privs_are ( fdw, user, privileges[] ) +CREATE OR REPLACE FUNCTION fdw_privs_are ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT fdw_privs_are( + $1, $2, $3, + 'Role ' || quote_ident($2) || ' should be granted ' + || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END + || ' on FDW ' || quote_ident($1) + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_server_privs (NAME, TEXT) +RETURNS TEXT[] AS $$ +BEGIN + IF pg_catalog.has_server_privilege($1, $2, 'USAGE') THEN + RETURN '{USAGE}'; + ELSE + RETURN '{}'; + END IF; +EXCEPTION WHEN undefined_object THEN + -- Same error code for unknown user or server. So figure out which. + RETURN CASE WHEN SQLERRM LIKE '%' || $1 || '%' THEN + '{undefined_role}' + ELSE + '{undefined_server}' + END; +END; +$$ LANGUAGE plpgsql; + +-- server_privs_are ( server, user, privileges[], description ) +CREATE OR REPLACE FUNCTION server_privs_are ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_server_privs( $2, $1::TEXT ); +BEGIN + IF grants[1] = 'undefined_server' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Server ' || quote_ident($1) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_role' THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + ' Role ' || quote_ident($2) || ' does not exist' + ); + END IF; + RETURN _assets_are('privileges', grants, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- server_privs_are ( server, user, privileges[] ) +CREATE OR REPLACE FUNCTION server_privs_are ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT server_privs_are( + $1, $2, $3, + 'Role ' || quote_ident($2) || ' should be granted ' + || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END + || ' on server ' || quote_ident($1) + ); +$$ LANGUAGE SQL; + +-- materialized_views_are( schema, materialized_views, description ) +CREATE OR REPLACE FUNCTION materialized_views_are ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( 'Materialized views', _extras('m', $1, $2), _missing('m', $1, $2), $3); +$$ LANGUAGE SQL; + +-- materialized_views_are( materialized_views, description ) +CREATE OR REPLACE FUNCTION materialized_views_are ( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( 'Materialized views', _extras('m', $1), _missing('m', $1), $2); +$$ LANGUAGE SQL; + +-- materialized_views_are( schema, materialized_views ) +CREATE OR REPLACE FUNCTION materialized_views_are ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _are( + 'Materialized views', _extras('m', $1, $2), _missing('m', $1, $2), + 'Schema ' || quote_ident($1) || ' should have the correct materialized views' + ); +$$ LANGUAGE SQL; + +-- materialized_views_are( materialized_views ) +CREATE OR REPLACE FUNCTION materialized_views_are ( NAME[] ) +RETURNS TEXT AS $$ + SELECT _are( + 'Materialized views', _extras('m', $1), _missing('m', $1), + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct materialized views' + ); +$$ LANGUAGE SQL; + +-- materialized_view_owner_is ( schema, materialized_view, user, description ) +CREATE OR REPLACE FUNCTION materialized_view_owner_is ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_rel_owner('m'::char, $1, $2); +BEGIN + -- Make sure the materialized view exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $4) || E'\n' || diag( + E' Materialized view ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + END IF; + + RETURN is(owner, $3, $4); +END; +$$ LANGUAGE plpgsql; + +-- materialized_view_owner_is ( schema, materialized_view, user ) +CREATE OR REPLACE FUNCTION materialized_view_owner_is ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT materialized_view_owner_is( + $1, $2, $3, + 'Materialized view ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) + ); +$$ LANGUAGE sql; + +-- materialized_view_owner_is ( materialized_view, user, description ) +CREATE OR REPLACE FUNCTION materialized_view_owner_is ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +DECLARE + owner NAME := _get_rel_owner('m'::char, $1); +BEGIN + -- Make sure the materialized view exists. + IF owner IS NULL THEN + RETURN ok(FALSE, $3) || E'\n' || diag( + E' Materialized view ' || quote_ident($1) || ' does not exist' + ); + END IF; + + RETURN is(owner, $2, $3); +END; +$$ LANGUAGE plpgsql; + +-- materialized_view_owner_is ( materialized_view, user ) +CREATE OR REPLACE FUNCTION materialized_view_owner_is ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT materialized_view_owner_is( + $1, $2, + 'Materialized view ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) + ); +$$ LANGUAGE sql; + +-- has_materialized_view( schema, materialized_view, description ) +CREATE OR REPLACE FUNCTION has_materialized_view ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists( 'm', $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- has_materialized_view( materialized_view, description ) +CREATE OR REPLACE FUNCTION has_materialized_view ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists( 'm', $1 ), $2 ); +$$ LANGUAGE SQL; + +-- has_materialized_view( materialized_view ) +CREATE OR REPLACE FUNCTION has_materialized_view ( NAME ) +RETURNS TEXT AS $$ + SELECT has_materialized_view( $1, 'Materialized view ' || quote_ident($1) || ' should exist' ); +$$ LANGUAGE SQL; + +-- hasnt_materialized_view( schema, materialized_view, description ) +CREATE OR REPLACE FUNCTION hasnt_materialized_view ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists( 'm', $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_materialized_view( materialized_view, description ) +CREATE OR REPLACE FUNCTION hasnt_materialized_view ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists( 'm', $1 ), $2 ); +$$ LANGUAGE SQL; + +-- hasnt_materialized_view( materialized_view ) +CREATE OR REPLACE FUNCTION hasnt_materialized_view ( NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_materialized_view( $1, 'Materialized view ' || quote_ident($1) || ' should not exist' ); +$$ LANGUAGE SQL; + + +-- foreign_tables_are( schema, tables, description ) +CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( 'foreign tables', _extras('f', $1, $2), _missing('f', $1, $2), $3); +$$ LANGUAGE SQL; + +-- foreign_tables_are( tables, description ) +CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( 'foreign tables', _extras('f', $1), _missing('f', $1), $2); +$$ LANGUAGE SQL; + +-- foreign_tables_are( schema, tables ) +CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _are( + 'foreign tables', _extras('f', $1, $2), _missing('f', $1, $2), + 'Schema ' || quote_ident($1) || ' should have the correct foreign tables' + ); +$$ LANGUAGE SQL; + +-- foreign_tables_are( tables ) +CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME[] ) +RETURNS TEXT AS $$ + SELECT _are( + 'foreign tables', _extras('f', $1), _missing('f', $1), + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct foreign tables' + ); +$$ LANGUAGE SQL; + +GRANT SELECT ON tap_funky TO PUBLIC; +GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; + +-- Get extensions in a given schema +CREATE OR REPLACE FUNCTION _extensions( NAME ) +RETURNS SETOF NAME AS $$ + SELECT e.extname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_extension e ON n.oid = e.extnamespace + WHERE n.nspname = $1 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _extensions() +RETURNS SETOF NAME AS $$ + SELECT extname FROM pg_catalog.pg_extension +$$ LANGUAGE SQL; + +-- extensions_are( schema, extensions, description ) +CREATE OR REPLACE FUNCTION extensions_are( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'extensions', + ARRAY(SELECT _extensions($1) EXCEPT SELECT unnest($2)), + ARRAY(SELECT unnest($2) EXCEPT SELECT _extensions($1)), + $3 + ); +$$ LANGUAGE SQL; + +-- extensions_are( schema, extensions) +CREATE OR REPLACE FUNCTION extensions_are( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT extensions_are( + $1, $2, + 'Schema ' || quote_ident($1) || ' should have the correct extensions' + ); +$$ LANGUAGE SQL; + +-- extensions_are( extensions, description ) +CREATE OR REPLACE FUNCTION extensions_are( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'extensions', + ARRAY(SELECT _extensions() EXCEPT SELECT unnest($1)), + ARRAY(SELECT unnest($1) EXCEPT SELECT _extensions()), + $2 + ); +$$ LANGUAGE SQL; + +-- extensions_are( schema, extensions) +CREATE OR REPLACE FUNCTION extensions_are( NAME[] ) +RETURNS TEXT AS $$ + SELECT extensions_are($1, 'Should have the correct extensions'); +$$ LANGUAGE SQL; + +-- check extension exists function with schema name +CREATE OR REPLACE FUNCTION _ext_exists( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS ( + SELECT TRUE + FROM pg_catalog.pg_extension ex + JOIN pg_catalog.pg_namespace n ON ex.extnamespace = n.oid + WHERE n.nspname = $1 + AND ex.extname = $2 + ); +$$ LANGUAGE SQL; + +-- check extension exists function without schema name +CREATE OR REPLACE FUNCTION _ext_exists( NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS ( + SELECT TRUE + FROM pg_catalog.pg_extension ex + WHERE ex.extname = $1 + ); +$$ LANGUAGE SQL; + +-- has_extension( schema, name, description ) +CREATE OR REPLACE FUNCTION has_extension( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _ext_exists( $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- has_extension( schema, name ) +CREATE OR REPLACE FUNCTION has_extension( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _ext_exists( $1, $2 ), + 'Extension ' || quote_ident($2) + || ' should exist in schema ' || quote_ident($1) ); +$$ LANGUAGE SQL; + +-- has_extension( name, description ) +CREATE OR REPLACE FUNCTION has_extension( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _ext_exists( $1 ), $2) +$$ LANGUAGE SQL; + +-- has_extension( name ) +CREATE OR REPLACE FUNCTION has_extension( NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _ext_exists( $1 ), + 'Extension ' || quote_ident($1) || ' should exist' ); +$$ LANGUAGE SQL; + +-- hasnt_extension( schema, name, description ) +CREATE OR REPLACE FUNCTION hasnt_extension( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _ext_exists( $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_extension( schema, name ) +CREATE OR REPLACE FUNCTION hasnt_extension( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _ext_exists( $1, $2 ), + 'Extension ' || quote_ident($2) + || ' should not exist in schema ' || quote_ident($1) ); +$$ LANGUAGE SQL; + +-- hasnt_extension( name, description ) +CREATE OR REPLACE FUNCTION hasnt_extension( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _ext_exists( $1 ), $2) +$$ LANGUAGE SQL; + +-- hasnt_extension( name ) +CREATE OR REPLACE FUNCTION hasnt_extension( NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _ext_exists( $1 ), + 'Extension ' || quote_ident($1) || ' should not exist' ); +$$ LANGUAGE SQL; + +-- is_partitioned( schema, table, description ) +CREATE OR REPLACE FUNCTION is_partitioned ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists('p', $1, $2), $3); +$$ LANGUAGE sql; + +-- is_partitioned( schema, table ) +CREATE OR REPLACE FUNCTION is_partitioned ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _rexists('p', $1, $2), + 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should be partitioned' + ); +$$ LANGUAGE sql; + +-- is_partitioned( table, description ) +CREATE OR REPLACE FUNCTION is_partitioned ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists('p', $1), $2); +$$ LANGUAGE sql; + +-- is_partitioned( table ) +CREATE OR REPLACE FUNCTION is_partitioned ( NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _rexists('p', $1), + 'Table ' || quote_ident($1) || ' should be partitioned' + ); +$$ LANGUAGE sql; + +-- isnt_partitioned( schema, table, description ) +CREATE OR REPLACE FUNCTION isnt_partitioned ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists('p', $1, $2), $3); +$$ LANGUAGE sql; + +-- isnt_partitioned( schema, table ) +CREATE OR REPLACE FUNCTION isnt_partitioned ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _rexists('p', $1, $2), + 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should not be partitioned' + ); +$$ LANGUAGE sql; + +-- isnt_partitioned( table, description ) +CREATE OR REPLACE FUNCTION isnt_partitioned ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists('p', $1), $2); +$$ LANGUAGE sql; + +-- isnt_partitioned( table ) +CREATE OR REPLACE FUNCTION isnt_partitioned ( NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _rexists('p', $1), + 'Table ' || quote_ident($1) || ' should not be partitioned' + ); +$$ LANGUAGE sql; + +-- _partof( child_schema, child_table, parent_schema, parent_table ) +CREATE OR REPLACE FUNCTION _partof ( NAME, NAME, NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_namespace cn + JOIN pg_catalog.pg_class cc ON cn.oid = cc.relnamespace + JOIN pg_catalog.pg_inherits i ON cc.oid = i.inhrelid + JOIN pg_catalog.pg_class pc ON i.inhparent = pc.oid + JOIN pg_catalog.pg_namespace pn ON pc.relnamespace = pn.oid + WHERE cn.nspname = $1 + AND cc.relname = $2 + AND cc.relispartition + AND pn.nspname = $3 + AND pc.relname = $4 + AND pc.relkind = 'p' + ) +$$ LANGUAGE sql; + +-- _partof( child_table, parent_table ) +CREATE OR REPLACE FUNCTION _partof ( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_class cc + JOIN pg_catalog.pg_inherits i ON cc.oid = i.inhrelid + JOIN pg_catalog.pg_class pc ON i.inhparent = pc.oid + WHERE cc.relname = $1 + AND cc.relispartition + AND pc.relname = $2 + AND pc.relkind = 'p' + AND pg_catalog.pg_table_is_visible(cc.oid) + AND pg_catalog.pg_table_is_visible(pc.oid) + ) +$$ LANGUAGE sql; + +-- is_partition_of( child_schema, child_table, parent_schema, parent_table, description ) +CREATE OR REPLACE FUNCTION is_partition_of ( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _partof($1, $2, $3, $4), $5); +$$ LANGUAGE sql; + +-- is_partition_of( child_schema, child_table, parent_schema, parent_table ) +CREATE OR REPLACE FUNCTION is_partition_of ( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _partof($1, $2, $3, $4), + 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should be a partition of ' + || quote_ident($3) || '.' || quote_ident($4) + ); +$$ LANGUAGE sql; + +-- is_partition_of( child_table, parent_table, description ) +CREATE OR REPLACE FUNCTION is_partition_of ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _partof($1, $2), $3); +$$ LANGUAGE sql; + +-- is_partition_of( child_table, parent_table ) +CREATE OR REPLACE FUNCTION is_partition_of ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _partof($1, $2), + 'Table ' || quote_ident($1) || ' should be a partition of ' || quote_ident($2) + ); +$$ LANGUAGE sql; + +-- _parts(schema, table) +CREATE OR REPLACE FUNCTION _parts( NAME, NAME ) +RETURNS SETOF NAME AS $$ + SELECT i.inhrelid::regclass::name + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + JOIN pg_catalog.pg_inherits i ON c.oid = i.inhparent + WHERE n.nspname = $1 + AND c.relname = $2 + AND c.relkind = 'p' +$$ LANGUAGE SQL; + +-- _parts(table) +CREATE OR REPLACE FUNCTION _parts( NAME ) +RETURNS SETOF NAME AS $$ + SELECT i.inhrelid::regclass::name + FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_inherits i ON c.oid = i.inhparent + WHERE c.relname = $1 + AND c.relkind = 'p' + AND pg_catalog.pg_table_is_visible(c.oid) +$$ LANGUAGE SQL; + +-- partitions_are( schema, table, partitions, description ) +CREATE OR REPLACE FUNCTION partitions_are( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'partitions', + ARRAY(SELECT _parts($1, $2) EXCEPT SELECT unnest($3)), + ARRAY(SELECT unnest($3) EXCEPT SELECT _parts($1, $2)), + $4 + ); +$$ LANGUAGE SQL; + +-- partitions_are( schema, table, partitions ) +CREATE OR REPLACE FUNCTION partitions_are( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT partitions_are( + $1, $2, $3, + 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct partitions' + ); +$$ LANGUAGE SQL; + +-- partitions_are( table, partitions, description ) +CREATE OR REPLACE FUNCTION partitions_are( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'partitions', + ARRAY(SELECT _parts($1) EXCEPT SELECT unnest($2)), + ARRAY(SELECT unnest($2) EXCEPT SELECT _parts($1)), + $3 + ); +$$ LANGUAGE SQL; + +-- partitions_are( table, partitions ) +CREATE OR REPLACE FUNCTION partitions_are( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT partitions_are( + $1, $2, + 'Table ' || quote_ident($1) || ' should have the correct partitions' + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _ident_array_to_sorted_string( name[], text ) +RETURNS text AS $$ + SELECT array_to_string(ARRAY( + SELECT quote_ident($1[i]) + FROM generate_series(1, array_upper($1, 1)) s(i) + ORDER BY $1[i] + ), $2); +$$ LANGUAGE SQL immutable; + +CREATE OR REPLACE FUNCTION _array_to_sorted_string( name[], text ) +RETURNS text AS $$ + SELECT array_to_string(ARRAY( + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + ORDER BY $1[i] + ), $2); +$$ LANGUAGE SQL immutable; + +-- policies_are( schema, table, policies[], description ) +CREATE OR REPLACE FUNCTION policies_are( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'policies', + ARRAY( + SELECT p.polname + FROM pg_catalog.pg_policy p + JOIN pg_catalog.pg_class c ON c.oid = p.polrelid + JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = $1 + AND c.relname = $2 + EXCEPT + SELECT $3[i] + FROM generate_series(1, array_upper($3, 1)) s(i) + ), + ARRAY( + SELECT $3[i] + FROM generate_series(1, array_upper($3, 1)) s(i) + EXCEPT + SELECT p.polname + FROM pg_catalog.pg_policy p + JOIN pg_catalog.pg_class c ON c.oid = p.polrelid + JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = $1 + AND c.relname = $2 + ), + $4 + ); +$$ LANGUAGE SQL; + +-- policies_are( schema, table, policies[] ) +CREATE OR REPLACE FUNCTION policies_are( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT policies_are( $1, $2, $3, 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct policies' ); +$$ LANGUAGE SQL; + +-- policies_are( table, policies[], description ) +CREATE OR REPLACE FUNCTION policies_are( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'policies', + ARRAY( + SELECT p.polname + FROM pg_catalog.pg_policy p + JOIN pg_catalog.pg_class c ON c.oid = p.polrelid + JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname = $1 + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + EXCEPT + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + ), + ARRAY( + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + EXCEPT + SELECT p.polname + FROM pg_catalog.pg_policy p + JOIN pg_catalog.pg_class c ON c.oid = p.polrelid + JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + ), + $3 + ); +$$ LANGUAGE SQL; + +-- policies_are( table, policies[] ) +CREATE OR REPLACE FUNCTION policies_are( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT policies_are( $1, $2, 'Table ' || quote_ident($1) || ' should have the correct policies' ); +$$ LANGUAGE SQL; + +-- policy_roles_are( schema, table, policy, roles[], description ) +CREATE OR REPLACE FUNCTION policy_roles_are( NAME, NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'policy roles', + ARRAY( + SELECT pr.rolname + FROM pg_catalog.pg_policy AS pp + JOIN pg_catalog.pg_roles AS pr ON pr.oid = ANY (pp.polroles) + JOIN pg_catalog.pg_class AS pc ON pc.oid = pp.polrelid + JOIN pg_catalog.pg_namespace AS pn ON pn.oid = pc.relnamespace + WHERE pn.nspname = $1 + AND pc.relname = $2 + AND pp.polname = $3 + EXCEPT + SELECT $4[i] + FROM generate_series(1, array_upper($4, 1)) s(i) + ), + ARRAY( + SELECT $4[i] + FROM generate_series(1, array_upper($4, 1)) s(i) + EXCEPT + SELECT pr.rolname + FROM pg_catalog.pg_policy AS pp + JOIN pg_catalog.pg_roles AS pr ON pr.oid = ANY (pp.polroles) + JOIN pg_catalog.pg_class AS pc ON pc.oid = pp.polrelid + JOIN pg_catalog.pg_namespace AS pn ON pn.oid = pc.relnamespace + WHERE pn.nspname = $1 + AND pc.relname = $2 + AND pp.polname = $3 + ), + $5 + ); +$$ LANGUAGE SQL; + +-- policy_roles_are( schema, table, policy, roles[] ) +CREATE OR REPLACE FUNCTION policy_roles_are( NAME, NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT policy_roles_are( $1, $2, $3, $4, 'Policy ' || quote_ident($3) || ' for table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct roles' ); +$$ LANGUAGE SQL; + +-- policy_roles_are( table, policy, roles[], description ) +CREATE OR REPLACE FUNCTION policy_roles_are( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( + 'policy roles', + ARRAY( + SELECT pr.rolname + FROM pg_catalog.pg_policy AS pp + JOIN pg_catalog.pg_roles AS pr ON pr.oid = ANY (pp.polroles) + JOIN pg_catalog.pg_class AS pc ON pc.oid = pp.polrelid + JOIN pg_catalog.pg_namespace AS pn ON pn.oid = pc.relnamespace + WHERE pc.relname = $1 + AND pp.polname = $2 + AND pn.nspname NOT IN ('pg_catalog', 'information_schema') + EXCEPT + SELECT $3[i] + FROM generate_series(1, array_upper($3, 1)) s(i) + ), + ARRAY( + SELECT $3[i] + FROM generate_series(1, array_upper($3, 1)) s(i) + EXCEPT + SELECT pr.rolname + FROM pg_catalog.pg_policy AS pp + JOIN pg_catalog.pg_roles AS pr ON pr.oid = ANY (pp.polroles) + JOIN pg_catalog.pg_class AS pc ON pc.oid = pp.polrelid + JOIN pg_catalog.pg_namespace AS pn ON pn.oid = pc.relnamespace + WHERE pc.relname = $1 + AND pp.polname = $2 + AND pn.nspname NOT IN ('pg_catalog', 'information_schema') + ), + $4 + ); +$$ LANGUAGE SQL; + +-- policy_roles_are( table, policy, roles[] ) +CREATE OR REPLACE FUNCTION policy_roles_are( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT policy_roles_are( $1, $2, $3, 'Policy ' || quote_ident($2) || ' for table ' || quote_ident($1) || ' should have the correct roles' ); +$$ LANGUAGE SQL; + +-- policy_cmd_is( schema, table, policy, command, description ) +CREATE OR REPLACE FUNCTION policy_cmd_is( NAME, NAME, NAME, text, text ) +RETURNS TEXT AS $$ +DECLARE + cmd text; +BEGIN + SELECT + CASE pp.polcmd WHEN 'r' THEN 'SELECT' + WHEN 'a' THEN 'INSERT' + WHEN 'w' THEN 'UPDATE' + WHEN 'd' THEN 'DELETE' + ELSE 'ALL' + END + FROM pg_catalog.pg_policy AS pp + JOIN pg_catalog.pg_class AS pc ON pc.oid = pp.polrelid + JOIN pg_catalog.pg_namespace AS pn ON pn.oid = pc.relnamespace + WHERE pn.nspname = $1 + AND pc.relname = $2 + AND pp.polname = $3 + INTO cmd; + + RETURN is( cmd, upper($4), $5 ); +END; +$$ LANGUAGE plpgsql; + +-- policy_cmd_is( schema, table, policy, command ) +CREATE OR REPLACE FUNCTION policy_cmd_is( NAME, NAME, NAME, text ) +RETURNS TEXT AS $$ + SELECT policy_cmd_is( + $1, $2, $3, $4, + 'Policy ' || quote_ident($3) + || ' for table ' || quote_ident($1) || '.' || quote_ident($2) + || ' should apply to ' || upper($4) || ' command' + ); +$$ LANGUAGE sql; + +-- policy_cmd_is( table, policy, command, description ) +CREATE OR REPLACE FUNCTION policy_cmd_is( NAME, NAME, text, text ) +RETURNS TEXT AS $$ +DECLARE + cmd text; +BEGIN + SELECT + CASE pp.polcmd WHEN 'r' THEN 'SELECT' + WHEN 'a' THEN 'INSERT' + WHEN 'w' THEN 'UPDATE' + WHEN 'd' THEN 'DELETE' + ELSE 'ALL' + END + FROM pg_catalog.pg_policy AS pp + JOIN pg_catalog.pg_class AS pc ON pc.oid = pp.polrelid + JOIN pg_catalog.pg_namespace AS pn ON pn.oid = pc.relnamespace + WHERE pc.relname = $1 + AND pp.polname = $2 + AND pn.nspname NOT IN ('pg_catalog', 'information_schema') + INTO cmd; + + RETURN is( cmd, upper($3), $4 ); +END; +$$ LANGUAGE plpgsql; + +-- policy_cmd_is( table, policy, command ) +CREATE OR REPLACE FUNCTION policy_cmd_is( NAME, NAME, text ) +RETURNS TEXT AS $$ + SELECT policy_cmd_is( + $1, $2, $3, + 'Policy ' || quote_ident($2) + || ' for table ' || quote_ident($1) + || ' should apply to ' || upper($3) || ' command' + ); +$$ LANGUAGE sql; + +/******************** INHERITANCE ***********************************************/ +/* + * Internal function to test whether the specified table in the specified schema + * has an inheritance chain. Returns true or false. + */ +CREATE OR REPLACE FUNCTION _inherited( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + WHERE c.relkind = 'r' + AND n.nspname = $1 + AND c.relname = $2 + AND c.relhassubclass = true + ); +$$ LANGUAGE SQL; + +/* + * Internal function to test whether a specific table in the search_path has an + * inheritance chain. Returns true or false. + */ +CREATE OR REPLACE FUNCTION _inherited( NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_class c + WHERE c.relkind = 'r' + AND pg_catalog.pg_table_is_visible( c.oid ) + AND c.relname = $1 + AND c.relhassubclass = true + ); +$$ LANGUAGE SQL; + +-- has_inherited_tables( schema, table, description ) +CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _inherited( $1, $2 ), $3); +$$ LANGUAGE SQL; + +-- has_inherited_tables( schema, table ) +CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _inherited( $1, $2 ), + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || ' should have descendents' + ); +$$ LANGUAGE SQL; + +-- has_inherited_tables( table, description ) +CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _inherited( $1 ), $2 ); +$$ LANGUAGE SQL; + +-- has_inherited_tables( table ) +CREATE OR REPLACE FUNCTION has_inherited_tables( NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _inherited( $1 ), + 'Table ' || quote_ident( $1 ) || ' should have descendents' + ); +$$ LANGUAGE SQL; + +-- hasnt_inherited_tables( schema, table, description ) +CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _inherited( $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_inherited_tables( schema, table ) +CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _inherited( $1, $2 ), + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || ' should not have descendents' + ); +$$ LANGUAGE SQL; + +-- hasnt_inherited_tables( table, description ) +CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _inherited( $1 ), $2 ); +$$ LANGUAGE SQL; + +-- hasnt_inherited_tables( table ) +CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _inherited( $1 ), + 'Table ' || quote_ident( $1 ) || ' should not have descendents' + ); +$$ LANGUAGE SQL; + +/* +* Internal function to test whether the schema-qualified table is an ancestor of +* the other schema-qualified table. The integer value is the length of the +* inheritance chain: a direct ancestor has has a chain length of 1. +*/ +CREATE OR REPLACE FUNCTION _ancestor_of( NAME, NAME, NAME, NAME, INT ) +RETURNS BOOLEAN AS $$ + WITH RECURSIVE inheritance_chain AS ( + -- select the ancestor tuple + SELECT i.inhrelid AS descendent_id, 1 AS inheritance_level + FROM pg_catalog.pg_inherits i + WHERE i.inhparent = ( + SELECT c1.oid + FROM pg_catalog.pg_class c1 + JOIN pg_catalog.pg_namespace n1 + ON c1.relnamespace = n1.oid + WHERE c1.relname = $2 + AND n1.nspname = $1 + ) + UNION + -- select the descendents + SELECT i.inhrelid AS descendent_id, + p.inheritance_level + 1 AS inheritance_level + FROM pg_catalog.pg_inherits i + JOIN inheritance_chain p + ON p.descendent_id = i.inhparent + WHERE i.inhrelid = ( + SELECT c1.oid + FROM pg_catalog.pg_class c1 + JOIN pg_catalog.pg_namespace n1 + ON c1.relnamespace = n1.oid + WHERE c1.relname = $4 + AND n1.nspname = $3 + ) + ) + SELECT EXISTS( + SELECT true + FROM inheritance_chain + WHERE inheritance_level = COALESCE($5, inheritance_level) + AND descendent_id = ( + SELECT c1.oid + FROM pg_catalog.pg_class c1 + JOIN pg_catalog.pg_namespace n1 + ON c1.relnamespace = n1.oid + WHERE c1.relname = $4 + AND n1.nspname = $3 + ) + ); +$$ LANGUAGE SQL; + +/* + * Internal function to check if not-qualified tables + * within the search_path are connected by an inheritance chain. + */ +CREATE OR REPLACE FUNCTION _ancestor_of( NAME, NAME, INT ) +RETURNS BOOLEAN AS $$ + WITH RECURSIVE inheritance_chain AS ( + -- select the ancestor tuple + SELECT i.inhrelid AS descendent_id, 1 AS inheritance_level + FROM pg_catalog.pg_inherits i + WHERE i.inhparent = ( + SELECT c1.oid + FROM pg_catalog.pg_class c1 + JOIN pg_catalog.pg_namespace n1 + ON c1.relnamespace = n1.oid + WHERE c1.relname = $1 + AND pg_catalog.pg_table_is_visible( c1.oid ) + ) + UNION + -- select the descendents + SELECT i.inhrelid AS descendent_id, + p.inheritance_level + 1 AS inheritance_level + FROM pg_catalog.pg_inherits i + JOIN inheritance_chain p + ON p.descendent_id = i.inhparent + WHERE i.inhrelid = ( + SELECT c1.oid + FROM pg_catalog.pg_class c1 + JOIN pg_catalog.pg_namespace n1 + ON c1.relnamespace = n1.oid + WHERE c1.relname = $2 + AND pg_catalog.pg_table_is_visible( c1.oid ) + ) + ) + SELECT EXISTS( + SELECT true + FROM inheritance_chain + WHERE inheritance_level = COALESCE($3, inheritance_level) + AND descendent_id = ( + SELECT c1.oid + FROM pg_catalog.pg_class c1 + JOIN pg_catalog.pg_namespace n1 + ON c1.relnamespace = n1.oid + WHERE c1.relname = $2 + AND pg_catalog.pg_table_is_visible( c1.oid ) + ) + ); +$$ LANGUAGE SQL; + +-- is_ancestor_of( schema, table, schema, table, depth, description ) +CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _ancestor_of( $1, $2, $3, $4, $5 ), $6 ); +$$ LANGUAGE SQL; + +-- is_ancestor_of( schema, table, schema, table, depth ) +CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT ok( + _ancestor_of( $1, $2, $3, $4, $5 ), + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' should be ancestor ' || $5 || ' for ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) + ); +$$ LANGUAGE SQL; + +-- is_ancestor_of( schema, table, schema, table, description ) +CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _ancestor_of( $1, $2, $3, $4, NULL ), $5 ); +$$ LANGUAGE SQL; + +-- is_ancestor_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _ancestor_of( $1, $2, $3, $4, NULL ), + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' should be an ancestor of ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) + ); +$$ LANGUAGE SQL; + +-- is_ancestor_of( table, table, depth, description ) +CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _ancestor_of( $1, $2, $3 ), $4 ); +$$ LANGUAGE SQL; + +-- is_ancestor_of( table, table, depth ) +CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT ok( + _ancestor_of( $1, $2, $3 ), + 'Table ' || quote_ident( $1 ) || ' should be ancestor ' || $3 || ' of ' || quote_ident( $2) + ); +$$ LANGUAGE SQL; + +-- is_ancestor_of( table, table, description ) +CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _ancestor_of( $1, $2, NULL ), $3 ); +$$ LANGUAGE SQL; + +-- is_ancestor_of( table, table ) +CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _ancestor_of( $1, $2, NULL ), + 'Table ' || quote_ident( $1 ) || ' should be an ancestor of ' || quote_ident( $2) + ); +$$ LANGUAGE SQL; + +-- isnt_ancestor_of( schema, table, schema, table, depth, description ) +CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _ancestor_of( $1, $2, $3, $4, $5 ), $6 ); +$$ LANGUAGE SQL; + +-- isnt_ancestor_of( schema, table, schema, table, depth ) +CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _ancestor_of( $1, $2, $3, $4, $5 ), + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' should not be ancestor ' || $5 || ' for ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) + ); +$$ LANGUAGE SQL; + +-- isnt_ancestor_of( schema, table, schema, table, description ) +CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _ancestor_of( $1, $2, $3, $4, NULL ), $5 ); +$$ LANGUAGE SQL; + +-- isnt_ancestor_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _ancestor_of( $1, $2, $3, $4, NULL ), + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' should not be an ancestor of ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) + ); +$$ LANGUAGE SQL; + +-- isnt_ancestor_of( table, table, depth, description ) +CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _ancestor_of( $1, $2, $3 ), $4 ); +$$ LANGUAGE SQL; + +-- isnt_ancestor_of( table, table, depth ) +CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _ancestor_of( $1, $2, $3 ), + 'Table ' || quote_ident( $1 ) || ' should not be ancestor ' || $3 || ' of ' || quote_ident( $2) + ); +$$ LANGUAGE SQL; + +-- isnt_ancestor_of( table, table, description ) +CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _ancestor_of( $1, $2, NULL ), $3 ); +$$ LANGUAGE SQL; + +-- isnt_ancestor_of( table, table ) +CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _ancestor_of( $1, $2, NULL ), + 'Table ' || quote_ident( $1 ) || ' should not be an ancestor of ' || quote_ident( $2) + ); +$$ LANGUAGE SQL; + +-- is_descendent_of( schema, table, schema, table, depth, description ) +CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _ancestor_of( $3, $4, $1, $2, $5 ), $6 ); +$$ LANGUAGE SQL; + +-- is_descendent_of( schema, table, schema, table, depth ) +CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT ok( + _ancestor_of( $3, $4, $1, $2, $5 ), + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' should be descendent ' || $5 || ' from ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) + ); +$$ LANGUAGE SQL; + +-- is_descendent_of( schema, table, schema, table, description ) +CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _ancestor_of( $3, $4, $1, $2, NULL ), $5 ); +$$ LANGUAGE SQL; + +-- is_descendent_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _ancestor_of( $3, $4, $1, $2, NULL ), + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' should be a descendent of ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) + ); +$$ LANGUAGE SQL; + +-- is_descendent_of( table, table, depth, description ) +CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _ancestor_of( $2, $1, $3 ), $4 ); +$$ LANGUAGE SQL; + +-- is_descendent_of( table, table, depth ) +CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT ok( + _ancestor_of( $2, $1, $3 ), + 'Table ' || quote_ident( $1 ) || ' should be descendent ' || $3 || ' from ' || quote_ident( $2) + ); +$$ LANGUAGE SQL; + +-- is_descendent_of( table, table, description ) +CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _ancestor_of( $2, $1, NULL ), $3 ); +$$ LANGUAGE SQL; + +-- is_descendent_of( table, table ) +CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _ancestor_of( $2, $1, NULL ), + 'Table ' || quote_ident( $1 ) || ' should be a descendent of ' || quote_ident( $2) + ); +$$ LANGUAGE SQL; + +-- isnt_descendent_of( schema, table, schema, table, depth, description ) +CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok(NOT _ancestor_of( $3, $4, $1, $2, $5 ), $6 ); +$$ LANGUAGE SQL; + +-- isnt_descendent_of( schema, table, schema, table, depth ) +CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _ancestor_of( $3, $4, $1, $2, $5 ), + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' should not be descendent ' || $5 || ' from ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) + ); +$$ LANGUAGE SQL; + +-- isnt_descendent_of( schema, table, schema, table, description ) +CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok(NOT _ancestor_of( $3, $4, $1, $2, NULL ), $5 ); +$$ LANGUAGE SQL; + +-- isnt_descendent_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _ancestor_of( $3, $4, $1, $2, NULL ), + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' should not be a descendent of ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) + ); +$$ LANGUAGE SQL; + +-- isnt_descendent_of( table, table, depth, description ) +CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok(NOT _ancestor_of( $2, $1, $3 ), $4 ); +$$ LANGUAGE SQL; + +-- isnt_descendent_of( table, table, depth ) +CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _ancestor_of( $2, $1, $3 ), + 'Table ' || quote_ident( $1 ) || ' should not be descendent ' || $3 || ' from ' || quote_ident( $2) + ); +$$ LANGUAGE SQL; + +-- isnt_descendent_of( table, table, description ) +CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok(NOT _ancestor_of( $2, $1, NULL ), $3 ); +$$ LANGUAGE SQL; + +-- isnt_descendent_of( table, table ) +CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _ancestor_of( $2, $1, NULL ), + 'Table ' || quote_ident( $1 ) || ' should not be a descendent of ' || quote_ident( $2) + ); +$$ LANGUAGE SQL; + +-- is_normal_function( schema, function, args[], description ) +CREATE OR REPLACE FUNCTION is_normal_function ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, $3, _type_func('f', $1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- is_normal_function( schema, function, args[] ) +CREATE OR REPLACE FUNCTION is_normal_function( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _func_compare( + $1, $2, $3, + _type_func('f', $1, $2, $3), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should be a normal function' + ); +$$ LANGUAGE sql; + +-- is_normal_function( schema, function, description ) +CREATE OR REPLACE FUNCTION is_normal_function ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, _type_func('f', $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- is_normal_function( schema, function ) +CREATE OR REPLACE FUNCTION is_normal_function( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT _func_compare( + $1, $2, _type_func('f', $1, $2), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should be a normal function' + ); +$$ LANGUAGE sql; + +-- is_normal_function( function, args[], description ) +CREATE OR REPLACE FUNCTION is_normal_function ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, $2, _type_func('f', $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- is_normal_function( function, args[] ) +CREATE OR REPLACE FUNCTION is_normal_function( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _func_compare( + NULL, $1, $2, _type_func('f', $1, $2), + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should be a normal function' + ); +$$ LANGUAGE sql; + +-- is_normal_function( function, description ) +CREATE OR REPLACE FUNCTION is_normal_function( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, _type_func('f', $1), $2 ); +$$ LANGUAGE sql; + +-- is_normal_function( function ) +CREATE OR REPLACE FUNCTION is_normal_function( NAME ) +RETURNS TEXT AS $$ + SELECT _func_compare( + NULL, $1, _type_func('f', $1), + 'Function ' || quote_ident($1) || '() should be a normal function' + ); +$$ LANGUAGE sql; + +-- isnt_normal_function( schema, function, args[], description ) +CREATE OR REPLACE FUNCTION isnt_normal_function ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, $3, NOT _type_func('f', $1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- isnt_normal_function( schema, function, args[] ) +CREATE OR REPLACE FUNCTION isnt_normal_function( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _func_compare( + $1, $2, $3, NOT _type_func('f', $1, $2, $3), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should not be a normal function' + ); +$$ LANGUAGE sql; + +-- isnt_normal_function( schema, function, description ) +CREATE OR REPLACE FUNCTION isnt_normal_function ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, NOT _type_func('f', $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_normal_function( schema, function ) +CREATE OR REPLACE FUNCTION isnt_normal_function( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT _func_compare( + $1, $2, NOT _type_func('f', $1, $2), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not be a normal function' + ); +$$ LANGUAGE sql; + +-- isnt_normal_function( function, args[], description ) +CREATE OR REPLACE FUNCTION isnt_normal_function ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, $2, NOT _type_func('f', $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_normal_function( function, args[] ) +CREATE OR REPLACE FUNCTION isnt_normal_function( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _func_compare( + NULL, $1, $2, + NOT _type_func('f', $1, $2), + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should not be a normal function' + ); +$$ LANGUAGE sql; + +-- isnt_normal_function( function, description ) +CREATE OR REPLACE FUNCTION isnt_normal_function( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, NOT _type_func('f', $1), $2 ); +$$ LANGUAGE sql; + +-- isnt_normal_function( function ) +CREATE OR REPLACE FUNCTION isnt_normal_function( NAME ) +RETURNS TEXT AS $$ + SELECT _func_compare( + NULL, $1, NOT _type_func('f', $1), + 'Function ' || quote_ident($1) || '() should not be a normal function' + ); +$$ LANGUAGE sql; + +-- is_window( schema, function, args[], description ) +CREATE OR REPLACE FUNCTION is_window ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, $3, _type_func( 'w', $1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- is_window( schema, function, args[] ) +CREATE OR REPLACE FUNCTION is_window( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _func_compare( + $1, $2, $3, _type_func('w', $1, $2, $3), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should be a window function' + ); +$$ LANGUAGE sql; + +-- is_window( schema, function, description ) +CREATE OR REPLACE FUNCTION is_window ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, _type_func('w', $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- is_window( schema, function ) +CREATE OR REPLACE FUNCTION is_window( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT _func_compare( + $1, $2, _type_func('w', $1, $2), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should be a window function' + ); +$$ LANGUAGE sql; + +-- is_window( function, args[], description ) +CREATE OR REPLACE FUNCTION is_window ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, $2, _type_func('w', $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- is_window( function, args[] ) +CREATE OR REPLACE FUNCTION is_window( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _func_compare( + NULL, $1, $2, _type_func('w', $1, $2), + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should be a window function' + ); +$$ LANGUAGE sql; + +-- is_window( function, description ) +CREATE OR REPLACE FUNCTION is_window( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, _type_func('w', $1), $2 ); +$$ LANGUAGE sql; + +-- is_window( function ) +CREATE OR REPLACE FUNCTION is_window( NAME ) +RETURNS TEXT AS $$ + SELECT _func_compare( + NULL, $1, _type_func('w', $1), + 'Function ' || quote_ident($1) || '() should be a window function' + ); +$$ LANGUAGE sql; + +-- isnt_window( schema, function, args[], description ) +CREATE OR REPLACE FUNCTION isnt_window ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, $3, NOT _type_func('w', $1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- isnt_window( schema, function, args[] ) +CREATE OR REPLACE FUNCTION isnt_window( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _func_compare( + $1, $2, $3, NOT _type_func('w', $1, $2, $3), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should not be a window function' + ); +$$ LANGUAGE sql; + +-- isnt_window( schema, function, description ) +CREATE OR REPLACE FUNCTION isnt_window ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, NOT _type_func('w', $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_window( schema, function ) +CREATE OR REPLACE FUNCTION isnt_window( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT _func_compare( + $1, $2, NOT _type_func('w', $1, $2), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not be a window function' + ); +$$ LANGUAGE sql; + +-- isnt_window( function, args[], description ) +CREATE OR REPLACE FUNCTION isnt_window ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, $2, NOT _type_func('w', $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_window( function, args[] ) +CREATE OR REPLACE FUNCTION isnt_window( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _func_compare( + NULL, $1, $2, NOT _type_func('w', $1, $2), + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should not be a window function' + ); +$$ LANGUAGE sql; + +-- isnt_window( function, description ) +CREATE OR REPLACE FUNCTION isnt_window( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, NOT _type_func('w', $1), $2 ); +$$ LANGUAGE sql; + +-- isnt_window( function ) +CREATE OR REPLACE FUNCTION isnt_window( NAME ) +RETURNS TEXT AS $$ + SELECT _func_compare( + NULL, $1, NOT _type_func('w', $1), + 'Function ' || quote_ident($1) || '() should not be a window function' + ); +$$ LANGUAGE sql; + +-- is_procedure( schema, function, args[], description ) +CREATE OR REPLACE FUNCTION is_procedure ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, $3, _type_func( 'p', $1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- is_procedure( schema, function, args[] ) +CREATE OR REPLACE FUNCTION is_procedure( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _func_compare( + $1, $2, $3, _type_func('p', $1, $2, $3), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should be a procedure' + ); +$$ LANGUAGE sql; + +-- is_procedure( schema, function, description ) +CREATE OR REPLACE FUNCTION is_procedure ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, _type_func('p', $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- is_procedure( schema, function ) +CREATE OR REPLACE FUNCTION is_procedure( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT _func_compare( + $1, $2, _type_func('p', $1, $2), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should be a procedure' + ); +$$ LANGUAGE sql; + +-- is_procedure( function, args[], description ) +CREATE OR REPLACE FUNCTION is_procedure ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, $2, _type_func('p', $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- is_procedure( function, args[] ) +CREATE OR REPLACE FUNCTION is_procedure( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _func_compare( + NULL, $1, $2, _type_func('p', $1, $2), + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should be a procedure' + ); +$$ LANGUAGE sql; + +-- is_procedure( function, description ) +CREATE OR REPLACE FUNCTION is_procedure( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, _type_func('p', $1), $2 ); +$$ LANGUAGE sql; + +-- is_procedure( function ) +CREATE OR REPLACE FUNCTION is_procedure( NAME ) +RETURNS TEXT AS $$ + SELECT _func_compare( + NULL, $1, _type_func('p', $1), + 'Function ' || quote_ident($1) || '() should be a procedure' + ); +$$ LANGUAGE sql; + +-- isnt_procedure( schema, function, args[], description ) +CREATE OR REPLACE FUNCTION isnt_procedure ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, $3, NOT _type_func('p', $1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- isnt_procedure( schema, function, args[] ) +CREATE OR REPLACE FUNCTION isnt_procedure( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _func_compare( + $1, $2, $3, NOT _type_func('p', $1, $2, $3), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') should not be a procedure' + ); +$$ LANGUAGE sql; + +-- isnt_procedure( schema, function, description ) +CREATE OR REPLACE FUNCTION isnt_procedure ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, NOT _type_func('p', $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_procedure( schema, function ) +CREATE OR REPLACE FUNCTION isnt_procedure( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT _func_compare( + $1, $2, NOT _type_func('p', $1, $2), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not be a procedure' + ); +$$ LANGUAGE sql; + +-- isnt_procedure( function, args[], description ) +CREATE OR REPLACE FUNCTION isnt_procedure ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, $2, NOT _type_func('p', $1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_procedure( function, args[] ) +CREATE OR REPLACE FUNCTION isnt_procedure( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _func_compare( + NULL, $1, $2, NOT _type_func('p', $1, $2), + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should not be a procedure' + ); +$$ LANGUAGE sql; + +-- isnt_procedure( function, description ) +CREATE OR REPLACE FUNCTION isnt_procedure( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, NOT _type_func('p', $1), $2 ); +$$ LANGUAGE sql; + +-- isnt_procedure( function ) +CREATE OR REPLACE FUNCTION isnt_procedure( NAME ) +RETURNS TEXT AS $$ + SELECT _func_compare( + NULL, $1, NOT _type_func('p', $1), + 'Function ' || quote_ident($1) || '() should not be a procedure' + ); +$$ LANGUAGE sql; + +--added a three columns: "args", "returns", "langname" used in mock_func function +CREATE OR REPLACE VIEW tap_funky +AS +SELECT p.oid, + n.nspname AS schema, + p.proname AS name, + pg_get_userbyid(p.proowner) AS owner, + arg._types as args, + proc_return."returns", + p.prolang AS langoid, + p.proisstrict AS is_strict, + tap._prokind(p.oid) AS kind, + p.prosecdef AS is_definer, + p.proretset AS returns_set, + p.provolatile::character(1) AS volatility, + pg_function_is_visible(p.oid) AS is_visible, + l.lanname AS langname +FROM + pg_proc p +JOIN + pg_namespace n +ON + p.pronamespace = n.oid +LEFT JOIN + pg_language l +ON + l.oid = p.prolang +left join lateral ( + select string_agg(nullif(_type, '')::regtype::text, ', ') as _types + from unnest(regexp_split_to_array(p.proargtypes::text, ' ')) as _type +) as arg on true + LEFT JOIN LATERAL ( + SELECT (n.nspname::text || '.'::text) || p.proname::text AS qualified +) proc_name ON true +left join lateral ( + select + case + when n.nspname != 'pg_catalog' + then pg_get_function_result((concat(proc_name.qualified, '(', arg._types, ')')::regprocedure)::oid) + else null + end AS "returns" +) as proc_return on true; + +--this procedure creates a mock in place of a real function +create or replace procedure mock_func( + in _func_schema text + , in _func_name text + , in _func_args text + , in _return_value anyelement +) +--creates mock in place of a real function + LANGUAGE plpgsql +AS $procedure$ +declare + _mock_ddl text; + _func_result_type text; + _func_qualified_name text; + _func_language text; +begin + select + "returns" + , langname + into + _func_result_type + , _func_language + from + tap_funky + where + "schema" = _func_schema + and "name" = _func_name; + + _mock_ddl = ' + create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' + RETURNS ' || _func_result_type || ' + LANGUAGE ' || _func_language || ' + AS $function$ + select ' || quote_nullable(_return_value) || '::' || pg_typeof(_return_value) || '; + $function$;'; + execute _mock_ddl; +end $procedure$; + +CREATE OR REPLACE PROCEDURE fake_table( + IN _table_schema text[], + IN _table_name text[], + IN _make_table_empty boolean default false, + IN _drop_not_null boolean DEFAULT false, + IN _drop_collation boolean DEFAULT false +) +--It frees a table from any constraint (we call such a table as a fake) +--faked table is a full copy of _table_name, but has no any constraint +--without foreign and primary things you can do whatever you want in testing context + LANGUAGE plpgsql +AS $procedure$ +declare + _table record; + _fk_table record; + _fake_ddl text; + _not_null_ddl text; +begin + for _table in + select + quote_ident(table_schema) table_schema, + quote_ident(table_name) table_name, + table_schema table_schema_l, + table_name table_name_l + from + unnest(_table_schema, _table_name) as t(table_schema, table_name) + loop + for _fk_table in + -- collect all table's relations including primary key and unique constraint + select distinct * + from ( + select + fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord + from + pg_all_foreign_keys + where + fk_schema_name = _table.table_schema_l and fk_table_name = _table.table_name_l + union all + select + fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord + from + pg_all_foreign_keys + where + pk_schema_name = _table.table_schema_l and pk_table_name = _table.table_name_l + union all + select + table_schema, table_name, constraint_name, 2 as ord + from + information_schema.table_constraints + where + table_schema = _table.table_schema_l + and table_name = _table.table_name_l + and constraint_type in ('PRIMARY KEY', 'UNIQUE') + ) as t + order by ord + loop + _fake_ddl = 'alter table ' || _fk_table.table_schema || '.' || _fk_table.table_name || ' + drop constraint ' || _fk_table.constraint_name || ';'; + execute _fake_ddl; + end loop; + + --make table empty + if _make_table_empty then + _fake_ddl = 'truncate table ' || _table.table_schema || '.' || _table.table_name || ';'; + execute _fake_ddl; + end if; + + --Free table from not null constraints + _fake_ddl = 'alter table ' || _table.table_schema || '.' || _table.table_name || ' '; + if _drop_not_null then + select + string_agg('alter column ' || t.attname || ' drop not null', ', ') + into + _not_null_ddl + from + pg_catalog.pg_attribute t + where t.attrelid = (_table.table_schema || '.' || _table.table_name)::regclass + and t.attnum > 0 and attnotnull; + + _fake_ddl = _fake_ddl || _not_null_ddl || ';'; + else + _fake_ddl = null; + end if; + + if _fake_ddl is not null then + execute _fake_ddl; + end if; + end loop; +end $procedure$; + +CREATE OR REPLACE FUNCTION _runner(text[], text[], text[], text[], text[]) + RETURNS SETOF text + LANGUAGE plpgsql +AS $function$ +DECLARE + startup ALIAS FOR $1; + shutdown ALIAS FOR $2; + setup ALIAS FOR $3; + teardown ALIAS FOR $4; + tests ALIAS FOR $5; + tap TEXT; + tfaild INTEGER := 0; + ffaild INTEGER := 0; + tnumb INTEGER := 0; + fnumb INTEGER := 0; + tok BOOLEAN := TRUE; +BEGIN + BEGIN + -- No plan support. + PERFORM * FROM no_plan(); + FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; + EXCEPTION + -- Catch all exceptions and simply rethrow custom exceptions. This + -- will roll back everything in the above block. + WHEN raise_exception THEN RAISE EXCEPTION '%', SQLERRM; + END; + + -- Record how startup tests have failed. + tfaild := num_failed(); + + FOR i IN 1..COALESCE(array_upper(tests, 1), 0) LOOP + + -- What subtest are we running? + RETURN NEXT diag_test_name('Subtest: ' || tests[i]); + + -- Reset the results. + tok := TRUE; + tnumb := COALESCE(_get('curr_test'), 0); + + IF tnumb > 0 THEN + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; + PERFORM _set('curr_test', 0); + PERFORM _set('failed', 0); + END IF; + + DECLARE + errstate text; + errmsg text; + detail text; + hint text; + context text; + schname text; + tabname text; + colname text; + chkname text; + typname text; + BEGIN + BEGIN + -- Run the setup functions. + FOR tap IN SELECT * FROM _runem(setup, false) LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Create a temp table to collect call count + execute 'create temp table call_count( + routine_schema text not null + , routine_name text not null + , call_cnt int + ) ON COMMIT DROP;'; + + -- Run the actual test function. + FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Run the teardown functions. + FOR tap IN SELECT * FROM _runem(teardown, false) LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Emit the plan. + fnumb := COALESCE(_get('curr_test'), 0); + RETURN NEXT ' 1..' || fnumb; + + -- Emit any error messages. + IF fnumb = 0 THEN + RETURN NEXT ' # No tests run!'; + tok = false; + ELSE + -- Report failures. + ffaild := num_failed(); + IF ffaild > 0 THEN + tok := FALSE; + RETURN NEXT ' ' || diag( + 'Looks like you failed ' || ffaild || ' test' || + CASE ffaild WHEN 1 THEN '' ELSE 's' END + || ' of ' || fnumb + ); + END IF; + END IF; + + EXCEPTION WHEN OTHERS THEN + -- Something went wrong. Record that fact. + errstate := SQLSTATE; + errmsg := SQLERRM; + GET STACKED DIAGNOSTICS + detail = PG_EXCEPTION_DETAIL, + hint = PG_EXCEPTION_HINT, + context = PG_EXCEPTION_CONTEXT, + schname = SCHEMA_NAME, + tabname = TABLE_NAME, + colname = COLUMN_NAME, + chkname = CONSTRAINT_NAME, + typname = PG_DATATYPE_NAME; + END; + + -- Always raise an exception to rollback any changes. + RAISE EXCEPTION '__TAP_ROLLBACK__'; + + EXCEPTION WHEN raise_exception THEN + IF errmsg IS NOT NULL THEN + -- Something went wrong. Emit the error message. + tok := FALSE; + RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( + errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname + )), '^', ' ', 'gn'); + errmsg := NULL; + END IF; + END; + + -- Restore the sequence. + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; + PERFORM _set('curr_test', tnumb); + PERFORM _set('failed', tfaild); + + -- Record this test. + RETURN NEXT ok(tok, tests[i]); + IF NOT tok THEN tfaild := tfaild + 1; END IF; + + END LOOP; + + -- Run the shutdown functions. + FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; + + -- Finish up. + FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP + RETURN NEXT tap; + END LOOP; + + -- Clean up and return. + PERFORM _cleanup(); + RETURN; +END; +$function$; + +create or replace procedure willing_count_calls_of(_proc_schema text, _proc_name text, _proc_args text) +--it counts of calls of a routine during execution + LANGUAGE plpgsql +AS $procedure$ +declare + _ddl text; +begin + insert into call_count(routine_schema, routine_name, call_cnt) values(_proc_schema, _proc_name, 0); + _ddl = ' + create or replace procedure ' || quote_ident(_proc_schema) || '.' || quote_ident(_proc_name) || _proc_args || ' + LANGUAGE plpgsql + AS $proc$ + begin + update call_count set call_cnt = call_cnt + 1 + where routine_schema = ' || quote_literal(_proc_schema) || ' + and routine_name = ' || quote_literal(_proc_name) || '; + end + $proc$;'; + execute _ddl; +end +$procedure$; + +create or replace function called_once(_proc_schema text, _proc_name text) +--Insures that a routine have been called only once +returns setof text as $$ +begin + return query select called_times(1, _proc_schema, _proc_name); +end $$ +LANGUAGE plpgsql; + +create or replace function called_times(_call_count int, _proc_schema text, _proc_name text) +--Insures that a routine have been called specified times +returns setof text as $$ +declare + _actual_call_count int; +begin + select + call_cnt + into + _actual_call_count + from + call_count + where + routine_schema = _proc_schema + and routine_name = _proc_name; + + return query select ok( + _actual_call_count = _call_count + , format('routine %L.%L must have been called %L times', _proc_schema, _proc_name, _call_count) + ); +end $$ +LANGUAGE plpgsql; + +create or replace function drop_prepared_statement(_statement_name text) +--It drops a prepared statement to be sure you can run a test many times +returns bool as $$ +begin + if exists(select * from pg_prepared_statements where "name" = _statement_name) then + EXECUTE format('deallocate %I;', _statement_name); + return true; + end if; + return false; +end +$$ +language plpgsql; + +create or replace procedure print_table_as_json(in _table_schema text, in _table_name text) + language plpgsql +AS $procedure$ +declare + _ddl text; + _json text; + _columns text; +--returns a query which you can execute and see your table as normal dataset +--note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows +begin + _ddl = ' + select json_agg( + array(select ' || quote_ident(_table_name) || ' from ' || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || ' limit 1000 + )) as j;'; + execute _ddl into _json; + _json = '[' || ltrim(rtrim(_json::text, ']'), '[') || ']'; + + select string_agg(concat(c.column_name, ' ', case when lower(c.data_type) = 'array' then e.data_type || '[]' else c.data_type end), ', ') + into _columns + from information_schema."columns" c + left join information_schema.element_types e + on ((c.table_catalog, c.table_schema, c.table_name, 'TABLE', c.dtd_identifier) + = (e.object_catalog, e.object_schema, e.object_name, e.object_type, e.collection_type_identifier)) + where c.table_schema = _table_schema + and c.table_name = _table_name; + + _json = $$select * from /*$$ || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || $$*/ json_to_recordset('$$ || _json || $$') as t($$ || _columns || $$)$$; + raise notice '%', _json; +end $procedure$; \ No newline at end of file From 4c29b12146d3b8965225d128cd4dcf8d8b5f8362 Mon Sep 17 00:00:00 2001 From: v-maliutin Date: Wed, 1 Jan 2025 10:24:21 +0000 Subject: [PATCH 03/20] there are several functions to fake a table and mock a function --- pgtap--1.3.1--1.3.2.sql | 452 -- pgtap--unpackaged--0.91.0.sql | 722 -- pgtap.sql.in | 11870 ---------------------------- sql/pgtap--1.3.3--1.3.4.sql | 418 + sql/pgtap--unpackaged--0.91.0.sql | 8 + sql/pgtap.sql.in | 422 + 6 files changed, 848 insertions(+), 13044 deletions(-) delete mode 100644 pgtap--1.3.1--1.3.2.sql delete mode 100644 pgtap--unpackaged--0.91.0.sql delete mode 100644 pgtap.sql.in diff --git a/pgtap--1.3.1--1.3.2.sql b/pgtap--1.3.1--1.3.2.sql deleted file mode 100644 index b85bc5cb3..000000000 --- a/pgtap--1.3.1--1.3.2.sql +++ /dev/null @@ -1,452 +0,0 @@ -DROP FUNCTION parse_type(type text, OUT typid oid, OUT typmod int4); - -CREATE OR REPLACE FUNCTION format_type_string ( TEXT ) -RETURNS TEXT AS $$ -DECLARE - want_type TEXT := $1; - typmodin_arg cstring[]; - typmodin_func regproc; - typmod int; -BEGIN - IF want_type::regtype = 'interval'::regtype THEN - -- RAISE NOTICE 'cannot resolve: %', want_type; -- TODO - RETURN want_type; - END IF; - - -- Extract type modifier from type declaration and format as cstring[] literal. - typmodin_arg := translate(substring(want_type FROM '[(][^")]+[)]'), '()', '{}'); - - -- Find typmodin function for want_type. - SELECT typmodin INTO typmodin_func - FROM pg_catalog.pg_type - WHERE oid = want_type::regtype; - - IF typmodin_func = 0 THEN - -- Easy: types without typemods. - RETURN format_type(want_type::regtype, null); - END IF; - - -- Get typemod via type-specific typmodin function. - EXECUTE format('SELECT %I(%L)', typmodin_func, typmodin_arg) INTO typmod; - RETURN format_type(want_type::regtype, typmod); -EXCEPTION WHEN OTHERS THEN RETURN NULL; -END; -$$ LANGUAGE PLPGSQL STABLE; - ---added a three columns: "args", "returns", "langname" used in mock_func function -CREATE OR REPLACE VIEW tap_funky -AS -SELECT p.oid, - n.nspname AS schema, - p.proname AS name, - pg_get_userbyid(p.proowner) AS owner, - arg._types as args, - proc_return."returns", - p.prolang AS langoid, - p.proisstrict AS is_strict, - tap._prokind(p.oid) AS kind, - p.prosecdef AS is_definer, - p.proretset AS returns_set, - p.provolatile::character(1) AS volatility, - pg_function_is_visible(p.oid) AS is_visible, - l.lanname AS langname -FROM - pg_proc p -JOIN - pg_namespace n -ON - p.pronamespace = n.oid -LEFT JOIN - pg_language l -ON - l.oid = p.prolang -left join lateral ( - select string_agg(nullif(_type, '')::regtype::text, ', ') as _types - from unnest(regexp_split_to_array(p.proargtypes::text, ' ')) as _type -) as arg on true - LEFT JOIN LATERAL ( - SELECT (n.nspname::text || '.'::text) || p.proname::text AS qualified -) proc_name ON true -left join lateral ( - select - case - when n.nspname != 'pg_catalog' - then pg_get_function_result((concat(proc_name.qualified, '(', arg._types, ')')::regprocedure)::oid) - else null - end AS "returns" -) as proc_return on true; - ---this procedure creates a mock in place of a real function -create or replace procedure mock_func( - in _func_schema text - , in _func_name text - , in _func_args text - , in _return_value anyelement -) ---creates mock in place of a real function - LANGUAGE plpgsql -AS $procedure$ -declare - _mock_ddl text; - _func_result_type text; - _func_qualified_name text; - _func_language text; -begin - select - "returns" - , langname - into - _func_result_type - , _func_language - from - tap_funky - where - "schema" = _func_schema - and "name" = _func_name; - - _mock_ddl = ' - create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' - RETURNS ' || _func_result_type || ' - LANGUAGE ' || _func_language || ' - AS $function$ - select ' || quote_nullable(_return_value) || '::' || pg_typeof(_return_value) || '; - $function$;'; - execute _mock_ddl; -end $procedure$; - -CREATE OR REPLACE PROCEDURE fake_table( - IN _table_schema text[], - IN _table_name text[], - in _make_table_empty boolean default false, - IN _drop_not_null boolean DEFAULT false, - IN _drop_collation boolean DEFAULT false -) ---It frees a table from any constraint (we call such a table as a fake) ---faked table is a full copy of _table_name, but has no any constraint ---without foreign and primary things you can do whatever you want in testing context - LANGUAGE plpgsql -AS $procedure$ -declare - _table record; - _fk_table record; - _fake_ddl text; - _not_null_ddl text; -begin - for _table in - select - quote_ident(table_schema) table_schema, - quote_ident(table_name) table_name, - table_schema table_schema_l, - table_name table_name_l - from - unnest(_table_schema, _table_name) as t(table_schema, table_name) - loop - for _fk_table in - -- collect all table's relations including primary key and unique constraint - select distinct * - from ( - select - fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord - from - pg_all_foreign_keys - where - fk_schema_name = _table.table_schema_l and fk_table_name = _table.table_name_l - union all - select - fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord - from - pg_all_foreign_keys - where - pk_schema_name = _table.table_schema_l and pk_table_name = _table.table_name_l - union all - select - table_schema, table_name, constraint_name, 2 as ord - from - information_schema.table_constraints - where - table_schema = _table.table_schema_l - and table_name = _table.table_name_l - and constraint_type in ('PRIMARY KEY', 'UNIQUE') - ) as t - order by ord - loop - _fake_ddl = 'alter table ' || _fk_table.table_schema || '.' || _fk_table.table_name || ' - drop constraint ' || _fk_table.constraint_name || ';'; - execute _fake_ddl; - end loop; - - if _make_table_empty then - _fake_ddl = 'truncate table ' || _table.table_schema || '.' || _table.table_name || ';'; - execute _fake_ddl; - end if; - - --Free table from not null constraints - _fake_ddl = 'alter table ' || _table.table_schema || '.' || _table.table_name || ' '; - if _drop_not_null then - select - string_agg('alter column ' || t.attname || ' drop not null', ', ') - into - _not_null_ddl - from - pg_catalog.pg_attribute t - where t.attrelid = (_table.table_schema || '.' || _table.table_name)::regclass - and t.attnum > 0 and attnotnull; - - _fake_ddl = _fake_ddl || _not_null_ddl || ';'; - else - _fake_ddl = null; - end if; - - if _fake_ddl is not null then - execute _fake_ddl; - end if; - end loop; -end $procedure$; - -CREATE OR REPLACE FUNCTION _runner(text[], text[], text[], text[], text[]) - RETURNS SETOF text - LANGUAGE plpgsql -AS $function$ -DECLARE - startup ALIAS FOR $1; - shutdown ALIAS FOR $2; - setup ALIAS FOR $3; - teardown ALIAS FOR $4; - tests ALIAS FOR $5; - tap TEXT; - tfaild INTEGER := 0; - ffaild INTEGER := 0; - tnumb INTEGER := 0; - fnumb INTEGER := 0; - tok BOOLEAN := TRUE; -BEGIN - BEGIN - -- No plan support. - PERFORM * FROM no_plan(); - FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; - EXCEPTION - -- Catch all exceptions and simply rethrow custom exceptions. This - -- will roll back everything in the above block. - WHEN raise_exception THEN RAISE EXCEPTION '%', SQLERRM; - END; - - -- Record how startup tests have failed. - tfaild := num_failed(); - - FOR i IN 1..COALESCE(array_upper(tests, 1), 0) LOOP - - -- What subtest are we running? - RETURN NEXT diag_test_name('Subtest: ' || tests[i]); - - -- Reset the results. - tok := TRUE; - tnumb := COALESCE(_get('curr_test'), 0); - - IF tnumb > 0 THEN - EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; - PERFORM _set('curr_test', 0); - PERFORM _set('failed', 0); - END IF; - - DECLARE - errstate text; - errmsg text; - detail text; - hint text; - context text; - schname text; - tabname text; - colname text; - chkname text; - typname text; - BEGIN - BEGIN - -- Run the setup functions. - FOR tap IN SELECT * FROM _runem(setup, false) LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Create a temp table to collect call count - execute 'create temp table call_count( - routine_schema text not null - , routine_name text not null - , call_cnt int - ) ON COMMIT DROP;'; - - -- Run the actual test function. - FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Run the teardown functions. - FOR tap IN SELECT * FROM _runem(teardown, false) LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Emit the plan. - fnumb := COALESCE(_get('curr_test'), 0); - RETURN NEXT ' 1..' || fnumb; - - -- Emit any error messages. - IF fnumb = 0 THEN - RETURN NEXT ' # No tests run!'; - tok = false; - ELSE - -- Report failures. - ffaild := num_failed(); - IF ffaild > 0 THEN - tok := FALSE; - RETURN NEXT ' ' || diag( - 'Looks like you failed ' || ffaild || ' test' || - CASE ffaild WHEN 1 THEN '' ELSE 's' END - || ' of ' || fnumb - ); - END IF; - END IF; - - EXCEPTION WHEN OTHERS THEN - -- Something went wrong. Record that fact. - errstate := SQLSTATE; - errmsg := SQLERRM; - GET STACKED DIAGNOSTICS - detail = PG_EXCEPTION_DETAIL, - hint = PG_EXCEPTION_HINT, - context = PG_EXCEPTION_CONTEXT, - schname = SCHEMA_NAME, - tabname = TABLE_NAME, - colname = COLUMN_NAME, - chkname = CONSTRAINT_NAME, - typname = PG_DATATYPE_NAME; - END; - - -- Always raise an exception to rollback any changes. - RAISE EXCEPTION '__TAP_ROLLBACK__'; - - EXCEPTION WHEN raise_exception THEN - IF errmsg IS NOT NULL THEN - -- Something went wrong. Emit the error message. - tok := FALSE; - RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( - errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname - )), '^', ' ', 'gn'); - errmsg := NULL; - END IF; - END; - - -- Restore the sequence. - EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; - PERFORM _set('curr_test', tnumb); - PERFORM _set('failed', tfaild); - - -- Record this test. - RETURN NEXT ok(tok, tests[i]); - IF NOT tok THEN tfaild := tfaild + 1; END IF; - - END LOOP; - - -- Run the shutdown functions. - FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; - - -- Finish up. - FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP - RETURN NEXT tap; - END LOOP; - - -- Clean up and return. - PERFORM _cleanup(); - RETURN; -END; -$function$; - -create or replace procedure willing_count_calls_of(_proc_schema text, _proc_name text, _proc_args text) - LANGUAGE plpgsql -AS $procedure$ -declare - _ddl text; -begin - insert into call_count(routine_schema, routine_name, call_cnt) values(_proc_schema, _proc_name, 0); - _ddl = ' - create or replace procedure ' || quote_ident(_proc_schema) || '.' || quote_ident(_proc_name) || _proc_args || ' - LANGUAGE plpgsql - AS $proc$ - begin - update call_count set call_cnt = call_cnt + 1 - where routine_schema = ' || quote_literal(_proc_schema) || ' - and routine_name = ' || quote_literal(_proc_name) || '; - end - $proc$;'; - raise notice '%', _ddl; - execute _ddl; -end -$procedure$; - -create or replace function called_once(_proc_schema text, _proc_name text) -returns setof text as $$ -begin - return query select called_times(1, _proc_schema, _proc_name); -end $$ -LANGUAGE plpgsql; - -create or replace function called_times(_call_count int, _proc_schema text, _proc_name text) -returns setof text as $$ -declare - _actual_call_count int; -begin - select - call_cnt - into - _actual_call_count - from - call_count - where - routine_schema = _proc_schema - and routine_name = _proc_name; - - return query select ok( - _actual_call_count = _call_count - , format('routine %L.%L must have been called %L times', _proc_schema, _proc_name, _call_count) - ); -end $$ -LANGUAGE plpgsql; - -create or replace function drop_prepared_statement(_statement_name text) -returns bool as $$ -begin - if exists(select * from pg_prepared_statements where "name" = _statement_name) then - EXECUTE format('deallocate %I;', _statement_name); - return true; - end if; - return false; -end -$$ -language plpgsql; - -create or replace procedure print_table_as_json(in _table_schema text, in _table_name text) - language plpgsql -AS $procedure$ -declare - _ddl text; - _json text; - _columns text; ---returns a query which you can execute and see your table as normal dataset ---note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows -begin - _ddl = ' - select json_agg( - array(select ' || quote_ident(_table_name) || ' from ' || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || ' limit 1000 - )) as j;'; - execute _ddl into _json; - _json = '[' || ltrim(rtrim(_json::text, ']'), '[') || ']'; - - select string_agg(concat(c.column_name, ' ', case when lower(c.data_type) = 'array' then e.data_type || '[]' else c.data_type end), ', ') - into _columns - from information_schema."columns" c - left join information_schema.element_types e - on ((c.table_catalog, c.table_schema, c.table_name, 'TABLE', c.dtd_identifier) - = (e.object_catalog, e.object_schema, e.object_name, e.object_type, e.collection_type_identifier)) - where c.table_schema = _table_schema - and c.table_name = _table_name; - - _json = $$select * from /*$$ || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || $$*/ json_to_recordset('$$ || _json || $$') as t($$ || _columns || $$)$$; - raise notice '%', _json; -end $procedure$; \ No newline at end of file diff --git a/pgtap--unpackaged--0.91.0.sql b/pgtap--unpackaged--0.91.0.sql deleted file mode 100644 index cec4c09a2..000000000 --- a/pgtap--unpackaged--0.91.0.sql +++ /dev/null @@ -1,722 +0,0 @@ -ALTER EXTENSION pgtap ADD FUNCTION pg_version(); -ALTER EXTENSION pgtap ADD FUNCTION pg_version_num(); -ALTER EXTENSION pgtap ADD FUNCTION os_name(); -ALTER EXTENSION pgtap ADD FUNCTION pgtap_version(); -ALTER EXTENSION pgtap ADD FUNCTION plan( integer ); -ALTER EXTENSION pgtap ADD FUNCTION no_plan(); -ALTER EXTENSION pgtap ADD FUNCTION _get ( text ); -ALTER EXTENSION pgtap ADD FUNCTION _get_latest ( text ); -ALTER EXTENSION pgtap ADD FUNCTION _get_latest ( text, integer ); -ALTER EXTENSION pgtap ADD FUNCTION _get_note ( text ); -ALTER EXTENSION pgtap ADD FUNCTION _get_note ( integer ); -ALTER EXTENSION pgtap ADD FUNCTION _set ( text, integer, text ); -ALTER EXTENSION pgtap ADD FUNCTION _set ( text, integer ); -ALTER EXTENSION pgtap ADD FUNCTION _set ( integer, integer ); -ALTER EXTENSION pgtap ADD FUNCTION _add ( text, integer, text ); -ALTER EXTENSION pgtap ADD FUNCTION _add ( text, integer ); -ALTER EXTENSION pgtap ADD FUNCTION add_result ( bool, bool, text, text, text ); -ALTER EXTENSION pgtap ADD FUNCTION num_failed (); -ALTER EXTENSION pgtap ADD FUNCTION _finish ( INTEGER, INTEGER, INTEGER); -ALTER EXTENSION pgtap ADD FUNCTION finish (); -ALTER EXTENSION pgtap ADD FUNCTION diag ( msg text ); -ALTER EXTENSION pgtap ADD FUNCTION diag ( msg anyelement ); -ALTER EXTENSION pgtap ADD FUNCTION diag( VARIADIC text[] ); -ALTER EXTENSION pgtap ADD FUNCTION diag( VARIADIC anyarray ); -ALTER EXTENSION pgtap ADD FUNCTION ok ( boolean, text ); -ALTER EXTENSION pgtap ADD FUNCTION ok ( boolean ); -ALTER EXTENSION pgtap ADD FUNCTION is (anyelement, anyelement, text); -ALTER EXTENSION pgtap ADD FUNCTION is (anyelement, anyelement); -ALTER EXTENSION pgtap ADD FUNCTION isnt (anyelement, anyelement, text); -ALTER EXTENSION pgtap ADD FUNCTION isnt (anyelement, anyelement); -ALTER EXTENSION pgtap ADD FUNCTION _alike ( BOOLEAN, ANYELEMENT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION matches ( anyelement, text, text ); -ALTER EXTENSION pgtap ADD FUNCTION matches ( anyelement, text ); -ALTER EXTENSION pgtap ADD FUNCTION imatches ( anyelement, text, text ); -ALTER EXTENSION pgtap ADD FUNCTION imatches ( anyelement, text ); -ALTER EXTENSION pgtap ADD FUNCTION alike ( anyelement, text, text ); -ALTER EXTENSION pgtap ADD FUNCTION alike ( anyelement, text ); -ALTER EXTENSION pgtap ADD FUNCTION ialike ( anyelement, text, text ); -ALTER EXTENSION pgtap ADD FUNCTION ialike ( anyelement, text ); -ALTER EXTENSION pgtap ADD FUNCTION _unalike ( BOOLEAN, ANYELEMENT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION doesnt_match ( anyelement, text, text ); -ALTER EXTENSION pgtap ADD FUNCTION doesnt_match ( anyelement, text ); -ALTER EXTENSION pgtap ADD FUNCTION doesnt_imatch ( anyelement, text, text ); -ALTER EXTENSION pgtap ADD FUNCTION doesnt_imatch ( anyelement, text ); -ALTER EXTENSION pgtap ADD FUNCTION unalike ( anyelement, text, text ); -ALTER EXTENSION pgtap ADD FUNCTION unalike ( anyelement, text ); -ALTER EXTENSION pgtap ADD FUNCTION unialike ( anyelement, text, text ); -ALTER EXTENSION pgtap ADD FUNCTION unialike ( anyelement, text ); -ALTER EXTENSION pgtap ADD FUNCTION cmp_ok (anyelement, text, anyelement, text); -ALTER EXTENSION pgtap ADD FUNCTION cmp_ok (anyelement, text, anyelement); -ALTER EXTENSION pgtap ADD FUNCTION pass ( text ); -ALTER EXTENSION pgtap ADD FUNCTION pass (); -ALTER EXTENSION pgtap ADD FUNCTION fail ( text ); -ALTER EXTENSION pgtap ADD FUNCTION fail (); -ALTER EXTENSION pgtap ADD FUNCTION todo ( why text, how_many int ); -ALTER EXTENSION pgtap ADD FUNCTION todo ( how_many int, why text ); -ALTER EXTENSION pgtap ADD FUNCTION todo ( why text ); -ALTER EXTENSION pgtap ADD FUNCTION todo ( how_many int ); -ALTER EXTENSION pgtap ADD FUNCTION todo_start (text); -ALTER EXTENSION pgtap ADD FUNCTION todo_start (); -ALTER EXTENSION pgtap ADD FUNCTION in_todo (); -ALTER EXTENSION pgtap ADD FUNCTION todo_end (); -ALTER EXTENSION pgtap ADD FUNCTION _todo(); -ALTER EXTENSION pgtap ADD FUNCTION skip ( why text, how_many int ); -ALTER EXTENSION pgtap ADD FUNCTION skip ( text ); -ALTER EXTENSION pgtap ADD FUNCTION skip( int, text ); -ALTER EXTENSION pgtap ADD FUNCTION skip( int ); -ALTER EXTENSION pgtap ADD FUNCTION _query( TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION throws_ok ( TEXT, CHAR(5), TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION throws_ok ( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION throws_ok ( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION throws_ok ( TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION throws_ok ( TEXT, int4, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION throws_ok ( TEXT, int4, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION throws_ok ( TEXT, int4 ); -ALTER EXTENSION pgtap ADD FUNCTION lives_ok ( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION lives_ok ( TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION performs_ok ( TEXT, NUMERIC, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION performs_ok ( TEXT, NUMERIC ); -ALTER EXTENSION pgtap ADD FUNCTION performs_within ( TEXT, NUMERIC, NUMERIC, INT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION performs_within ( TEXT, NUMERIC, NUMERIC, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION performs_within ( TEXT, NUMERIC, NUMERIC, INT ); -ALTER EXTENSION pgtap ADD FUNCTION performs_within ( TEXT, NUMERIC, NUMERIC ); -ALTER EXTENSION pgtap ADD FUNCTION _rexists ( CHAR, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _rexists ( CHAR, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_table ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_table ( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_table ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_table ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_table ( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_table ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_view ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_view ( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_view ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_view ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_view ( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_view ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_sequence ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_sequence ( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_sequence ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_sequence ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_sequence ( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_sequence ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _cexists ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _cexists ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_column ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_column ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_column ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_column ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_column ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_column ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _col_is_null ( NAME, NAME, NAME, TEXT, bool ); -ALTER EXTENSION pgtap ADD FUNCTION _col_is_null ( NAME, NAME, TEXT, bool ); -ALTER EXTENSION pgtap ADD FUNCTION col_not_null ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_not_null ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_not_null ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_null ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_null ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_null ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION display_type ( OID, INTEGER ); -ALTER EXTENSION pgtap ADD FUNCTION display_type ( NAME, OID, INTEGER ); -ALTER EXTENSION pgtap ADD FUNCTION _get_col_type ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _get_col_type ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _get_col_ns_type ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _quote_ident_like(TEXT, TEXT); -ALTER EXTENSION pgtap ADD FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_type_is ( NAME, NAME, NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_type_is ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_type_is ( NAME, NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_type_is ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _has_def ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _has_def ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION col_has_default ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_has_default ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_has_default ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION col_hasnt_default ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_hasnt_default ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_hasnt_default ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _cdi ( NAME, NAME, NAME, anyelement, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _cdi ( NAME, NAME, anyelement, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _cdi ( NAME, NAME, anyelement ); -ALTER EXTENSION pgtap ADD FUNCTION col_default_is ( NAME, NAME, NAME, anyelement, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_default_is ( NAME, NAME, NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_default_is ( NAME, NAME, anyelement, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_default_is ( NAME, NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_default_is ( NAME, NAME, anyelement ); -ALTER EXTENSION pgtap ADD FUNCTION col_default_is ( NAME, NAME, text ); -ALTER EXTENSION pgtap ADD FUNCTION _hasc ( NAME, NAME, CHAR ); -ALTER EXTENSION pgtap ADD FUNCTION _hasc ( NAME, CHAR ); -ALTER EXTENSION pgtap ADD FUNCTION has_pk ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_pk ( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_pk ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_pk ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_pk ( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_pk ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _ident_array_to_string( name[], text ); -ALTER EXTENSION pgtap ADD FUNCTION _pg_sv_column_array( OID, SMALLINT[] ); -ALTER EXTENSION pgtap ADD FUNCTION _pg_sv_table_accessible( OID, OID ); -ALTER EXTENSION pgtap ADD VIEW pg_all_foreign_keys; -ALTER EXTENSION pgtap ADD FUNCTION _keys ( NAME, NAME, CHAR ); -ALTER EXTENSION pgtap ADD FUNCTION _keys ( NAME, CHAR ); -ALTER EXTENSION pgtap ADD FUNCTION _ckeys ( NAME, NAME, CHAR ); -ALTER EXTENSION pgtap ADD FUNCTION _ckeys ( NAME, CHAR ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_pk ( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_pk ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_pk ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_pk ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_pk ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_pk ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION col_isnt_pk ( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_isnt_pk ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_isnt_pk ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION col_isnt_pk ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_isnt_pk ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_isnt_pk ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_fk ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_fk ( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_fk ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_fk ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_fk ( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_fk ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _fkexists ( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _fkexists ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_fk ( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_fk ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_fk ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_fk ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_fk ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_fk ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION col_isnt_fk ( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_isnt_fk ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_isnt_fk ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION col_isnt_fk ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_isnt_fk ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_isnt_fk ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_unique ( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_unique ( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_unique ( TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_unique ( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_unique ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_unique ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_unique ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_unique ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_is_unique ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_check ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_check ( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_check ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION col_has_check ( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_has_check ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_has_check ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION col_has_check ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_has_check ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION col_has_check ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION fk_ok ( NAME, NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION fk_ok ( NAME, NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD VIEW tap_funky; -ALTER EXTENSION pgtap ADD FUNCTION _got_func ( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _got_func ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _got_func ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _got_func ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_function ( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_function( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION has_function ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_function( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_function ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_function( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION has_function( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_function( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_function ( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_function( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_function ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_function( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_function ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_function( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_function( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_function( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _pg_sv_type_array( OID[] ); -ALTER EXTENSION pgtap ADD FUNCTION can ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION can ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION can ( NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION can ( NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _ikeys( NAME, NAME, NAME); -ALTER EXTENSION pgtap ADD FUNCTION _ikeys( NAME, NAME); -ALTER EXTENSION pgtap ADD FUNCTION _have_index( NAME, NAME, NAME); -ALTER EXTENSION pgtap ADD FUNCTION _have_index( NAME, NAME); -ALTER EXTENSION pgtap ADD FUNCTION _iexpr( NAME, NAME, NAME); -ALTER EXTENSION pgtap ADD FUNCTION _iexpr( NAME, NAME); -ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, NAME, NAME[], text ); -ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, NAME, NAME, text ); -ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, NAME[], text ); -ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _is_schema( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, NAME, text ); -ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME, text ); -ALTER EXTENSION pgtap ADD FUNCTION has_index ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_index ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_index ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_index ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_index ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION index_is_unique ( NAME, NAME, NAME, text ); -ALTER EXTENSION pgtap ADD FUNCTION index_is_unique ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION index_is_unique ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION index_is_unique ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION index_is_primary ( NAME, NAME, NAME, text ); -ALTER EXTENSION pgtap ADD FUNCTION index_is_primary ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION index_is_primary ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION index_is_primary ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION is_clustered ( NAME, NAME, NAME, text ); -ALTER EXTENSION pgtap ADD FUNCTION is_clustered ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION is_clustered ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION is_clustered ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION index_is_type ( NAME, NAME, NAME, NAME, text ); -ALTER EXTENSION pgtap ADD FUNCTION index_is_type ( NAME, NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION index_is_type ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION index_is_type ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _trig ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _trig ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_trigger ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_trigger ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_trigger ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_trigger ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_trigger ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_trigger ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_trigger ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_trigger ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME, text ); -ALTER EXTENSION pgtap ADD FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION trigger_is ( NAME, NAME, NAME, text ); -ALTER EXTENSION pgtap ADD FUNCTION trigger_is ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_schema( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_schema( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_schema( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_schema( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_tablespace( NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_tablespace( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_tablespace( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_tablespace( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_tablespace( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _has_type( NAME, NAME, CHAR[] ); -ALTER EXTENSION pgtap ADD FUNCTION _has_type( NAME, CHAR[] ); -ALTER EXTENSION pgtap ADD FUNCTION has_type( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_type( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_type( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_type( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_type( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_type( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_type( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_type( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_domain( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_domain( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_domain( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_domain( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_domain( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_domain( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_domain( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_domain( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_enum( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_enum( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_enum( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_enum( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_enum( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_enum( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_enum( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_enum( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION enum_has_labels( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION enum_has_labels( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION enum_has_labels( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION enum_has_labels( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _has_role( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_role( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_role( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_role( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_role( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _has_user( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_user( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_user( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_user( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_user( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _is_super( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION is_superuser( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION is_superuser( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION isnt_superuser( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION isnt_superuser( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _has_group( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_group( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_group( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_group( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_group( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _grolist ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION is_member_of( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION is_member_of( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION is_member_of( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION is_member_of( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _cmp_types(oid, name); -ALTER EXTENSION pgtap ADD FUNCTION _cast_exists ( NAME, NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _cast_exists ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _cast_exists ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_cast ( NAME, NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_cast ( NAME, NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_cast ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_cast ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_cast ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_cast ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_cast ( NAME, NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_cast ( NAME, NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_cast ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_cast ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_cast ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_cast ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _expand_context( char ); -ALTER EXTENSION pgtap ADD FUNCTION _get_context( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION cast_context_is( NAME, NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION cast_context_is( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _op_exists ( NAME, NAME, NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _op_exists ( NAME, NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _op_exists ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_operator ( NAME, NAME, NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_operator ( NAME, NAME, NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_operator ( NAME, NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_operator ( NAME, NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_operator ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_operator ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_leftop ( NAME, NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_leftop ( NAME, NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_leftop ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_leftop ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_leftop ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_leftop ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_rightop ( NAME, NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_rightop ( NAME, NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_rightop ( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_rightop ( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_rightop ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_rightop ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _are ( text, name[], name[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION tablespaces_are ( NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION tablespaces_are ( NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION schemas_are ( NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION schemas_are ( NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _extras ( CHAR, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _extras ( CHAR, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _missing ( CHAR, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _missing ( CHAR, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION tables_are ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION tables_are ( NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION tables_are ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION tables_are ( NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION views_are ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION views_are ( NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION views_are ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION views_are ( NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION sequences_are ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION sequences_are ( NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION sequences_are ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION sequences_are ( NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION functions_are ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION functions_are ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION functions_are ( NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION functions_are ( NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION indexes_are( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION indexes_are( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION indexes_are( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION indexes_are( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION users_are( NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION users_are( NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION groups_are( NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION groups_are( NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION languages_are( NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION languages_are( NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _is_trusted( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_language( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_language( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_language( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_language( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION language_is_trusted( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION language_is_trusted( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _opc_exists( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_opclass( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_opclass( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_opclass( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_opclass( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_opclass( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_opclass( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_opclass( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_opclass( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION opclasses_are ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION opclasses_are ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION opclasses_are ( NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION opclasses_are ( NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION rules_are( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION rules_are( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION rules_are( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION rules_are( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _is_instead( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _is_instead( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_rule( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_rule( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION has_rule( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION has_rule( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_rule( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_rule( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_rule( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION hasnt_rule( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION rule_is_instead( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION rule_is_instead( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION rule_is_instead( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION rule_is_instead( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _expand_on( char ); -ALTER EXTENSION pgtap ADD FUNCTION _contract_on( TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _rule_on( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _rule_on( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION rule_is_on( NAME, NAME, NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION rule_is_on( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION rule_is_on( NAME, NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION rule_is_on( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _nosuch( NAME, NAME, NAME[]); -ALTER EXTENSION pgtap ADD FUNCTION _func_compare( NAME, NAME, NAME[], anyelement, anyelement, TEXT); -ALTER EXTENSION pgtap ADD FUNCTION _func_compare( NAME, NAME, NAME[], boolean, TEXT); -ALTER EXTENSION pgtap ADD FUNCTION _func_compare( NAME, NAME, anyelement, anyelement, TEXT); -ALTER EXTENSION pgtap ADD FUNCTION _func_compare( NAME, NAME, boolean, TEXT); -ALTER EXTENSION pgtap ADD FUNCTION _lang ( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _lang ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _lang ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _lang ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION function_lang_is( NAME, NAME, NAME[], NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION function_lang_is( NAME, NAME, NAME[], NAME ); -ALTER EXTENSION pgtap ADD FUNCTION function_lang_is( NAME, NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION function_lang_is( NAME, NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION function_lang_is( NAME, NAME[], NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION function_lang_is( NAME, NAME[], NAME ); -ALTER EXTENSION pgtap ADD FUNCTION function_lang_is( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION function_lang_is( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _returns ( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _returns ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _returns ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _returns ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION function_returns( NAME, NAME, NAME[], TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION function_returns( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION function_returns( NAME, NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION function_returns( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION function_returns( NAME, NAME[], TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION function_returns( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION function_returns( NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION function_returns( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _definer ( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _definer ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _definer ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _definer ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION is_definer ( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION is_definer( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION is_definer ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION is_definer( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION is_definer ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION is_definer( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION is_definer( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION is_definer( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _agg ( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _agg ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _agg ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _agg ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION is_aggregate ( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION is_aggregate( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION is_aggregate ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION is_aggregate( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION is_aggregate ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION is_aggregate( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION is_aggregate( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION is_aggregate( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _strict ( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _strict ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _strict ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _strict ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION is_strict ( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION is_strict( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION is_strict ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION is_strict( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION is_strict ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION is_strict( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION is_strict( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION is_strict( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _expand_vol( char ); -ALTER EXTENSION pgtap ADD FUNCTION _refine_vol( text ); -ALTER EXTENSION pgtap ADD FUNCTION _vol ( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _vol ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _vol ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _vol ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION volatility_is( NAME, NAME, NAME[], TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION volatility_is( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION volatility_is( NAME, NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION volatility_is( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION volatility_is( NAME, NAME[], TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION volatility_is( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION volatility_is( NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION volatility_is( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT, BOOLEAN ); -ALTER EXTENSION pgtap ADD FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION check_test( TEXT, BOOLEAN, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION check_test( TEXT, BOOLEAN ); -ALTER EXTENSION pgtap ADD FUNCTION findfuncs( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION findfuncs( TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _runem( text[], boolean ); -ALTER EXTENSION pgtap ADD FUNCTION _is_verbose(); -ALTER EXTENSION pgtap ADD FUNCTION do_tap( name, text ); -ALTER EXTENSION pgtap ADD FUNCTION do_tap( name ); -ALTER EXTENSION pgtap ADD FUNCTION do_tap( text ); -ALTER EXTENSION pgtap ADD FUNCTION do_tap( ); -ALTER EXTENSION pgtap ADD FUNCTION _currtest(); -ALTER EXTENSION pgtap ADD FUNCTION _cleanup(); -ALTER EXTENSION pgtap ADD FUNCTION diag_test_name(TEXT); -ALTER EXTENSION pgtap ADD FUNCTION _runner( text[], text[], text[], text[], text[] ); -ALTER EXTENSION pgtap ADD FUNCTION runtests( NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION runtests( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION runtests( TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION runtests( ); -ALTER EXTENSION pgtap ADD FUNCTION _temptable ( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _temptable ( anyarray, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _temptypes( TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _docomp( TEXT, TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _relcomp( TEXT, anyarray, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION set_eq( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION set_eq( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION set_eq( TEXT, anyarray, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION set_eq( TEXT, anyarray ); -ALTER EXTENSION pgtap ADD FUNCTION bag_eq( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION bag_eq( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION bag_eq( TEXT, anyarray, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION bag_eq( TEXT, anyarray ); -ALTER EXTENSION pgtap ADD FUNCTION _do_ne( TEXT, TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _relne( TEXT, TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _relne( TEXT, anyarray, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION set_ne( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION set_ne( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION set_ne( TEXT, anyarray, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION set_ne( TEXT, anyarray ); -ALTER EXTENSION pgtap ADD FUNCTION bag_ne( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION bag_ne( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION bag_ne( TEXT, anyarray, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION bag_ne( TEXT, anyarray ); -ALTER EXTENSION pgtap ADD FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION set_has( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION set_has( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION bag_has( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION bag_has( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION set_hasnt( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION set_hasnt( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION bag_hasnt( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION bag_hasnt( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION results_eq( refcursor, refcursor, text ); -ALTER EXTENSION pgtap ADD FUNCTION results_eq( refcursor, refcursor ); -ALTER EXTENSION pgtap ADD FUNCTION results_eq( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION results_eq( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION results_eq( TEXT, anyarray, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION results_eq( TEXT, anyarray ); -ALTER EXTENSION pgtap ADD FUNCTION results_eq( TEXT, refcursor, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION results_eq( TEXT, refcursor ); -ALTER EXTENSION pgtap ADD FUNCTION results_eq( refcursor, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION results_eq( refcursor, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION results_eq( refcursor, anyarray, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION results_eq( refcursor, anyarray ); -ALTER EXTENSION pgtap ADD FUNCTION results_ne( refcursor, refcursor, text ); -ALTER EXTENSION pgtap ADD FUNCTION results_ne( refcursor, refcursor ); -ALTER EXTENSION pgtap ADD FUNCTION results_ne( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION results_ne( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION results_ne( TEXT, anyarray, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION results_ne( TEXT, anyarray ); -ALTER EXTENSION pgtap ADD FUNCTION results_ne( TEXT, refcursor, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION results_ne( TEXT, refcursor ); -ALTER EXTENSION pgtap ADD FUNCTION results_ne( refcursor, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION results_ne( refcursor, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION results_ne( refcursor, anyarray, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION results_ne( refcursor, anyarray ); -ALTER EXTENSION pgtap ADD FUNCTION isa_ok( anyelement, regtype, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION isa_ok( anyelement, regtype ); -ALTER EXTENSION pgtap ADD FUNCTION is_empty( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION is_empty( TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION collect_tap( VARIADIC text[] ); -ALTER EXTENSION pgtap ADD FUNCTION collect_tap( VARCHAR[] ); -ALTER EXTENSION pgtap ADD FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION throws_like ( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION throws_like ( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION throws_ilike ( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION throws_ilike ( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION throws_matching ( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION throws_matching ( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION throws_imatching ( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION throws_imatching ( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION roles_are( NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION roles_are( NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _types_are ( NAME, NAME[], TEXT, CHAR[] ); -ALTER EXTENSION pgtap ADD FUNCTION types_are ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION types_are ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _types_are ( NAME[], TEXT, CHAR[] ); -ALTER EXTENSION pgtap ADD FUNCTION types_are ( NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION types_are ( NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION domains_are ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION domains_are ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION domains_are ( NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION domains_are ( NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION enums_are ( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION enums_are ( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION enums_are ( NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION enums_are ( NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _dexists ( NAME, NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _dexists ( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION _get_dtype( NAME, TEXT, BOOLEAN ); -ALTER EXTENSION pgtap ADD FUNCTION _get_dtype( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION domain_type_is( NAME, TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION domain_type_is( NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION domain_type_is( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION domain_type_is( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION domain_type_isnt( NAME, TEXT, NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION domain_type_isnt( NAME, TEXT, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION domain_type_isnt( NAME, TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION domain_type_isnt( NAME, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION domain_type_isnt( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION domain_type_isnt( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION row_eq( TEXT, anyelement, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION row_eq( TEXT, anyelement ); -ALTER EXTENSION pgtap ADD FUNCTION triggers_are( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION triggers_are( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION triggers_are( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION triggers_are( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _areni ( text, text[], text[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION casts_are ( TEXT[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION casts_are ( TEXT[] ); -ALTER EXTENSION pgtap ADD FUNCTION display_oper ( NAME, OID ); -ALTER EXTENSION pgtap ADD FUNCTION operators_are( NAME, TEXT[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION operators_are ( NAME, TEXT[] ); -ALTER EXTENSION pgtap ADD FUNCTION operators_are( TEXT[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION operators_are ( TEXT[] ); -ALTER EXTENSION pgtap ADD FUNCTION columns_are( NAME, NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION columns_are( NAME, NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION columns_are( NAME, NAME[], TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION columns_are( NAME, NAME[] ); -ALTER EXTENSION pgtap ADD FUNCTION _get_db_owner( NAME ); -ALTER EXTENSION pgtap ADD FUNCTION db_owner_is ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION db_owner_is ( NAME, NAME ); - -ALTER EXTENSION pgtap ADD PROCEDURE mock_func( TEXT, TEXT, TEXT, ANYELEMENT ); -ALTER EXTENSION pgtap ADD PROCEDURE fake_table( TEXT[], TEXT[], BOOL, BOOL ); -ALTER EXTENSION pgtap ADD PROCEDURE willing_count_calls_of( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION called_once( TEXT , TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION called_times( INT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION drop_prepared_statement( TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION print_table_as_json( TEXT, TEXT ); \ No newline at end of file diff --git a/pgtap.sql.in b/pgtap.sql.in deleted file mode 100644 index ed94e3728..000000000 --- a/pgtap.sql.in +++ /dev/null @@ -1,11870 +0,0 @@ --- This file defines pgTAP, a collection of functions for TAP-based unit --- testing. It is distributed under the revised FreeBSD license. --- --- The home page for the pgTAP project is: --- --- https://pgtap.org/ - -CREATE OR REPLACE FUNCTION pg_version() -RETURNS text AS 'SELECT current_setting(''server_version'')' -LANGUAGE SQL IMMUTABLE; - -CREATE OR REPLACE FUNCTION pg_version_num() -RETURNS integer AS $$ - SELECT current_setting('server_version_num')::integer; -$$ LANGUAGE SQL IMMUTABLE; - -CREATE OR REPLACE FUNCTION os_name() -RETURNS TEXT AS 'SELECT ''__OS__''::text;' -LANGUAGE SQL IMMUTABLE; - -CREATE OR REPLACE FUNCTION pgtap_version() -RETURNS NUMERIC AS 'SELECT __VERSION__;' -LANGUAGE SQL IMMUTABLE; - -CREATE OR REPLACE FUNCTION plan( integer ) -RETURNS TEXT AS $$ -DECLARE - rcount INTEGER; -BEGIN - BEGIN - EXECUTE ' - CREATE TEMP SEQUENCE __tcache___id_seq; - CREATE TEMP TABLE __tcache__ ( - id INTEGER NOT NULL DEFAULT nextval(''__tcache___id_seq''), - label TEXT NOT NULL, - value INTEGER NOT NULL, - note TEXT NOT NULL DEFAULT '''' - ); - CREATE UNIQUE INDEX __tcache___key ON __tcache__(id); - GRANT ALL ON TABLE __tcache__ TO PUBLIC; - GRANT ALL ON TABLE __tcache___id_seq TO PUBLIC; - - CREATE TEMP SEQUENCE __tresults___numb_seq; - GRANT ALL ON TABLE __tresults___numb_seq TO PUBLIC; - '; - - EXCEPTION WHEN duplicate_table THEN - -- Raise an exception if there's already a plan. - EXECUTE 'SELECT TRUE FROM __tcache__ WHERE label = ''plan'''; - GET DIAGNOSTICS rcount = ROW_COUNT; - IF rcount > 0 THEN - RAISE EXCEPTION 'You tried to plan twice!'; - END IF; - END; - - -- Save the plan and return. - PERFORM _set('plan', $1 ); - PERFORM _set('failed', 0 ); - RETURN '1..' || $1; -END; -$$ LANGUAGE plpgsql strict; - -CREATE OR REPLACE FUNCTION no_plan() -RETURNS SETOF boolean AS $$ -BEGIN - PERFORM plan(0); - RETURN; -END; -$$ LANGUAGE plpgsql strict; - -CREATE OR REPLACE FUNCTION _get ( text ) -RETURNS integer AS $$ -DECLARE - ret integer; -BEGIN - EXECUTE 'SELECT value FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' INTO ret; - RETURN ret; -END; -$$ LANGUAGE plpgsql strict; - -CREATE OR REPLACE FUNCTION _get_latest ( text ) -RETURNS integer[] AS $$ -DECLARE - ret integer[]; -BEGIN - EXECUTE 'SELECT ARRAY[id, value] FROM __tcache__ WHERE label = ' || - quote_literal($1) || ' AND id = (SELECT MAX(id) FROM __tcache__ WHERE label = ' || - quote_literal($1) || ') LIMIT 1' INTO ret; - RETURN ret; -EXCEPTION WHEN undefined_table THEN - RAISE EXCEPTION 'You tried to run a test without a plan! Gotta have a plan'; -END; -$$ LANGUAGE plpgsql strict; - -CREATE OR REPLACE FUNCTION _get_latest ( text, integer ) -RETURNS integer AS $$ -DECLARE - ret integer; -BEGIN - EXECUTE 'SELECT MAX(id) FROM __tcache__ WHERE label = ' || - quote_literal($1) || ' AND value = ' || $2 INTO ret; - RETURN ret; -END; -$$ LANGUAGE plpgsql strict; - -CREATE OR REPLACE FUNCTION _get_note ( text ) -RETURNS text AS $$ -DECLARE - ret text; -BEGIN - EXECUTE 'SELECT note FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' INTO ret; - RETURN ret; -END; -$$ LANGUAGE plpgsql strict; - -CREATE OR REPLACE FUNCTION _get_note ( integer ) -RETURNS text AS $$ -DECLARE - ret text; -BEGIN - EXECUTE 'SELECT note FROM __tcache__ WHERE id = ' || $1 || ' LIMIT 1' INTO ret; - RETURN ret; -END; -$$ LANGUAGE plpgsql strict; - -CREATE OR REPLACE FUNCTION _set ( text, integer, text ) -RETURNS integer AS $$ -DECLARE - rcount integer; -BEGIN - EXECUTE 'UPDATE __tcache__ SET value = ' || $2 - || CASE WHEN $3 IS NULL THEN '' ELSE ', note = ' || quote_literal($3) END - || ' WHERE label = ' || quote_literal($1); - GET DIAGNOSTICS rcount = ROW_COUNT; - IF rcount = 0 THEN - RETURN _add( $1, $2, $3 ); - END IF; - RETURN $2; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION _set ( text, integer ) -RETURNS integer AS $$ - SELECT _set($1, $2, '') -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _set ( integer, integer ) -RETURNS integer AS $$ -BEGIN - EXECUTE 'UPDATE __tcache__ SET value = ' || $2 - || ' WHERE id = ' || $1; - RETURN $2; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION _add ( text, integer, text ) -RETURNS integer AS $$ -BEGIN - EXECUTE 'INSERT INTO __tcache__ (label, value, note) values (' || - quote_literal($1) || ', ' || $2 || ', ' || quote_literal(COALESCE($3, '')) || ')'; - RETURN $2; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION _add ( text, integer ) -RETURNS integer AS $$ - SELECT _add($1, $2, '') -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION add_result ( bool, bool, text, text, text ) -RETURNS integer AS $$ -BEGIN - IF NOT $1 THEN PERFORM _set('failed', _get('failed') + 1); END IF; - RETURN nextval('__tresults___numb_seq'); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION num_failed () -RETURNS INTEGER AS $$ - SELECT _get('failed'); -$$ LANGUAGE SQL strict; - -CREATE OR REPLACE FUNCTION _finish (INTEGER, INTEGER, INTEGER, BOOLEAN DEFAULT NULL) -RETURNS SETOF TEXT AS $$ -DECLARE - curr_test ALIAS FOR $1; - exp_tests INTEGER := $2; - num_faild ALIAS FOR $3; - plural CHAR; - raise_ex ALIAS FOR $4; -BEGIN - plural := CASE exp_tests WHEN 1 THEN '' ELSE 's' END; - - IF curr_test IS NULL THEN - RAISE EXCEPTION '# No tests run!'; - END IF; - - IF exp_tests = 0 OR exp_tests IS NULL THEN - -- No plan. Output one now. - exp_tests = curr_test; - RETURN NEXT '1..' || exp_tests; - END IF; - - IF curr_test <> exp_tests THEN - RETURN NEXT diag( - 'Looks like you planned ' || exp_tests || ' test' || - plural || ' but ran ' || curr_test - ); - ELSIF num_faild > 0 THEN - IF raise_ex THEN - RAISE EXCEPTION '% test% failed of %', num_faild, CASE num_faild WHEN 1 THEN '' ELSE 's' END, exp_tests; - END IF; - RETURN NEXT diag( - 'Looks like you failed ' || num_faild || ' test' || - CASE num_faild WHEN 1 THEN '' ELSE 's' END - || ' of ' || exp_tests - ); - ELSE - - END IF; - RETURN; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION finish (exception_on_failure BOOLEAN DEFAULT NULL) -RETURNS SETOF TEXT AS $$ - SELECT * FROM _finish( - _get('curr_test'), - _get('plan'), - num_failed(), - $1 - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION diag ( msg text ) -RETURNS TEXT AS $$ - SELECT '# ' || replace( - replace( - replace( $1, E'\r\n', E'\n# ' ), - E'\n', - E'\n# ' - ), - E'\r', - E'\n# ' - ); -$$ LANGUAGE sql strict; - -CREATE OR REPLACE FUNCTION diag ( msg anyelement ) -RETURNS TEXT AS $$ - SELECT diag($1::text); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION diag( VARIADIC text[] ) -RETURNS TEXT AS $$ - SELECT diag(array_to_string($1, '')); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION diag( VARIADIC anyarray ) -RETURNS TEXT AS $$ - SELECT diag(array_to_string($1, '')); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION ok ( boolean, text ) -RETURNS TEXT AS $$ -DECLARE - aok ALIAS FOR $1; - descr text := $2; - test_num INTEGER; - todo_why TEXT; - ok BOOL; -BEGIN - todo_why := _todo(); - ok := CASE - WHEN aok = TRUE THEN aok - WHEN todo_why IS NULL THEN COALESCE(aok, false) - ELSE TRUE - END; - IF _get('plan') IS NULL THEN - RAISE EXCEPTION 'You tried to run a test without a plan! Gotta have a plan'; - END IF; - - test_num := add_result( - ok, - COALESCE(aok, false), - descr, - CASE WHEN todo_why IS NULL THEN '' ELSE 'todo' END, - COALESCE(todo_why, '') - ); - - RETURN (CASE aok WHEN TRUE THEN '' ELSE 'not ' END) - || 'ok ' || _set( 'curr_test', test_num ) - || CASE descr WHEN '' THEN '' ELSE COALESCE( ' - ' || substr(diag( descr ), 3), '' ) END - || COALESCE( ' ' || diag( 'TODO ' || todo_why ), '') - || CASE aok WHEN TRUE THEN '' ELSE E'\n' || - diag('Failed ' || - CASE WHEN todo_why IS NULL THEN '' ELSE '(TODO) ' END || - 'test ' || test_num || - CASE descr WHEN '' THEN '' ELSE COALESCE(': "' || descr || '"', '') END ) || - CASE WHEN aok IS NULL THEN E'\n' || diag(' (test result was NULL)') ELSE '' END - END; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION ok ( boolean ) -RETURNS TEXT AS $$ - SELECT ok( $1, NULL ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION is (anyelement, anyelement, text) -RETURNS TEXT AS $$ -DECLARE - result BOOLEAN; - output TEXT; -BEGIN - -- Would prefer $1 IS NOT DISTINCT FROM, but that's not supported by 8.1. - result := NOT $1 IS DISTINCT FROM $2; - output := ok( result, $3 ); - RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( - ' have: ' || CASE WHEN $1 IS NULL THEN 'NULL' ELSE $1::text END || - E'\n want: ' || CASE WHEN $2 IS NULL THEN 'NULL' ELSE $2::text END - ) END; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION is (anyelement, anyelement) -RETURNS TEXT AS $$ - SELECT is( $1, $2, NULL); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION isnt (anyelement, anyelement, text) -RETURNS TEXT AS $$ -DECLARE - result BOOLEAN; - output TEXT; -BEGIN - result := $1 IS DISTINCT FROM $2; - output := ok( result, $3 ); - RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( - ' have: ' || COALESCE( $1::text, 'NULL' ) || - E'\n want: anything else' - ) END; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION isnt (anyelement, anyelement) -RETURNS TEXT AS $$ - SELECT isnt( $1, $2, NULL); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _alike ( BOOLEAN, ANYELEMENT, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - result ALIAS FOR $1; - got ALIAS FOR $2; - rx ALIAS FOR $3; - descr ALIAS FOR $4; - output TEXT; -BEGIN - output := ok( result, descr ); - RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( - ' ' || COALESCE( quote_literal(got), 'NULL' ) || - E'\n doesn''t match: ' || COALESCE( quote_literal(rx), 'NULL' ) - ) END; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION matches ( anyelement, text, text ) -RETURNS TEXT AS $$ - SELECT _alike( $1 ~ $2, $1, $2, $3 ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION matches ( anyelement, text ) -RETURNS TEXT AS $$ - SELECT _alike( $1 ~ $2, $1, $2, NULL ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION imatches ( anyelement, text, text ) -RETURNS TEXT AS $$ - SELECT _alike( $1 ~* $2, $1, $2, $3 ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION imatches ( anyelement, text ) -RETURNS TEXT AS $$ - SELECT _alike( $1 ~* $2, $1, $2, NULL ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION alike ( anyelement, text, text ) -RETURNS TEXT AS $$ - SELECT _alike( $1 ~~ $2, $1, $2, $3 ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION alike ( anyelement, text ) -RETURNS TEXT AS $$ - SELECT _alike( $1 ~~ $2, $1, $2, NULL ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION ialike ( anyelement, text, text ) -RETURNS TEXT AS $$ - SELECT _alike( $1 ~~* $2, $1, $2, $3 ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION ialike ( anyelement, text ) -RETURNS TEXT AS $$ - SELECT _alike( $1 ~~* $2, $1, $2, NULL ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _unalike ( BOOLEAN, ANYELEMENT, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - result ALIAS FOR $1; - got ALIAS FOR $2; - rx ALIAS FOR $3; - descr ALIAS FOR $4; - output TEXT; -BEGIN - output := ok( result, descr ); - RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( - ' ' || COALESCE( quote_literal(got), 'NULL' ) || - E'\n matches: ' || COALESCE( quote_literal(rx), 'NULL' ) - ) END; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION doesnt_match ( anyelement, text, text ) -RETURNS TEXT AS $$ - SELECT _unalike( $1 !~ $2, $1, $2, $3 ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION doesnt_match ( anyelement, text ) -RETURNS TEXT AS $$ - SELECT _unalike( $1 !~ $2, $1, $2, NULL ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION doesnt_imatch ( anyelement, text, text ) -RETURNS TEXT AS $$ - SELECT _unalike( $1 !~* $2, $1, $2, $3 ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION doesnt_imatch ( anyelement, text ) -RETURNS TEXT AS $$ - SELECT _unalike( $1 !~* $2, $1, $2, NULL ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION unalike ( anyelement, text, text ) -RETURNS TEXT AS $$ - SELECT _unalike( $1 !~~ $2, $1, $2, $3 ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION unalike ( anyelement, text ) -RETURNS TEXT AS $$ - SELECT _unalike( $1 !~~ $2, $1, $2, NULL ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION unialike ( anyelement, text, text ) -RETURNS TEXT AS $$ - SELECT _unalike( $1 !~~* $2, $1, $2, $3 ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION unialike ( anyelement, text ) -RETURNS TEXT AS $$ - SELECT _unalike( $1 !~~* $2, $1, $2, NULL ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION cmp_ok (anyelement, text, anyelement, text) -RETURNS TEXT AS $$ -DECLARE - have ALIAS FOR $1; - op ALIAS FOR $2; - want ALIAS FOR $3; - descr ALIAS FOR $4; - result BOOLEAN; - output TEXT; -BEGIN - EXECUTE 'SELECT ' || - COALESCE(quote_literal( have ), 'NULL') || '::' || pg_typeof(have) || ' ' - || op || ' ' || - COALESCE(quote_literal( want ), 'NULL') || '::' || pg_typeof(want) - INTO result; - output := ok( COALESCE(result, FALSE), descr ); - RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( - ' ' || COALESCE( quote_literal(have), 'NULL' ) || - E'\n ' || op || - E'\n ' || COALESCE( quote_literal(want), 'NULL' ) - ) END; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION cmp_ok (anyelement, text, anyelement) -RETURNS TEXT AS $$ - SELECT cmp_ok( $1, $2, $3, NULL ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION pass ( text ) -RETURNS TEXT AS $$ - SELECT ok( TRUE, $1 ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION pass () -RETURNS TEXT AS $$ - SELECT ok( TRUE, NULL ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION fail ( text ) -RETURNS TEXT AS $$ - SELECT ok( FALSE, $1 ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION fail () -RETURNS TEXT AS $$ - SELECT ok( FALSE, NULL ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION todo ( why text, how_many int ) -RETURNS SETOF BOOLEAN AS $$ -BEGIN - PERFORM _add('todo', COALESCE(how_many, 1), COALESCE(why, '')); - RETURN; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION todo ( how_many int, why text ) -RETURNS SETOF BOOLEAN AS $$ -BEGIN - PERFORM _add('todo', COALESCE(how_many, 1), COALESCE(why, '')); - RETURN; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION todo ( why text ) -RETURNS SETOF BOOLEAN AS $$ -BEGIN - PERFORM _add('todo', 1, COALESCE(why, '')); - RETURN; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION todo ( how_many int ) -RETURNS SETOF BOOLEAN AS $$ -BEGIN - PERFORM _add('todo', COALESCE(how_many, 1), ''); - RETURN; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION todo_start (text) -RETURNS SETOF BOOLEAN AS $$ -BEGIN - PERFORM _add('todo', -1, COALESCE($1, '')); - RETURN; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION todo_start () -RETURNS SETOF BOOLEAN AS $$ -BEGIN - PERFORM _add('todo', -1, ''); - RETURN; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION in_todo () -RETURNS BOOLEAN AS $$ -DECLARE - todos integer; -BEGIN - todos := _get('todo'); - RETURN CASE WHEN todos IS NULL THEN FALSE ELSE TRUE END; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION todo_end () -RETURNS SETOF BOOLEAN AS $$ -DECLARE - id integer; -BEGIN - id := _get_latest( 'todo', -1 ); - IF id IS NULL THEN - RAISE EXCEPTION 'todo_end() called without todo_start()'; - END IF; - EXECUTE 'DELETE FROM __tcache__ WHERE id = ' || id; - RETURN; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION _todo() -RETURNS TEXT AS $$ -DECLARE - todos INT[]; - note text; -BEGIN - -- Get the latest id and value, because todo() might have been called - -- again before the todos ran out for the first call to todo(). This - -- allows them to nest. - todos := _get_latest('todo'); - IF todos IS NULL THEN - -- No todos. - RETURN NULL; - END IF; - IF todos[2] = 0 THEN - -- Todos depleted. Clean up. - EXECUTE 'DELETE FROM __tcache__ WHERE id = ' || todos[1]; - RETURN NULL; - END IF; - -- Decrement the count of counted todos and return the reason. - IF todos[2] <> -1 THEN - PERFORM _set(todos[1], todos[2] - 1); - END IF; - note := _get_note(todos[1]); - - IF todos[2] = 1 THEN - -- This was the last todo, so delete the record. - EXECUTE 'DELETE FROM __tcache__ WHERE id = ' || todos[1]; - END IF; - - RETURN note; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION skip ( why text, how_many int ) -RETURNS TEXT AS $$ -DECLARE - output TEXT[]; -BEGIN - output := '{}'; - FOR i IN 1..how_many LOOP - output = array_append( - output, - ok( TRUE ) || ' ' || diag( 'SKIP' || COALESCE( ' ' || why, '') ) - ); - END LOOP; - RETURN array_to_string(output, E'\n'); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION skip ( text ) -RETURNS TEXT AS $$ - SELECT ok( TRUE ) || ' ' || diag( 'SKIP' || COALESCE(' ' || $1, '') ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION skip( int, text ) -RETURNS TEXT AS 'SELECT skip($2, $1)' -LANGUAGE sql; - -CREATE OR REPLACE FUNCTION skip( int ) -RETURNS TEXT AS 'SELECT skip(NULL, $1)' -LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _query( TEXT ) -RETURNS TEXT AS $$ - SELECT CASE - WHEN $1 LIKE '"%' OR $1 !~ '[[:space:]]' THEN 'EXECUTE ' || $1 - ELSE $1 - END; -$$ LANGUAGE SQL; - --- throws_ok ( sql, errcode, errmsg, description ) -CREATE OR REPLACE FUNCTION throws_ok ( TEXT, CHAR(5), TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - query TEXT := _query($1); - errcode ALIAS FOR $2; - errmsg ALIAS FOR $3; - desctext ALIAS FOR $4; - descr TEXT; -BEGIN - descr := COALESCE( - desctext, - 'threw ' || errcode || ': ' || errmsg, - 'threw ' || errcode, - 'threw ' || errmsg, - 'threw an exception' - ); - EXECUTE query; - RETURN ok( FALSE, descr ) || E'\n' || diag( - ' caught: no exception' || - E'\n wanted: ' || COALESCE( errcode, 'an exception' ) - ); -EXCEPTION WHEN OTHERS OR ASSERT_FAILURE THEN - IF (errcode IS NULL OR SQLSTATE = errcode) - AND ( errmsg IS NULL OR SQLERRM = errmsg) - THEN - -- The expected errcode and/or message was thrown. - RETURN ok( TRUE, descr ); - ELSE - -- This was not the expected errcode or errmsg. - RETURN ok( FALSE, descr ) || E'\n' || diag( - ' caught: ' || SQLSTATE || ': ' || SQLERRM || - E'\n wanted: ' || COALESCE( errcode, 'an exception' ) || - COALESCE( ': ' || errmsg, '') - ); - END IF; -END; -$$ LANGUAGE plpgsql; - --- throws_ok ( sql, errcode, errmsg ) --- throws_ok ( sql, errmsg, description ) -CREATE OR REPLACE FUNCTION throws_ok ( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ -BEGIN - IF octet_length($2) = 5 THEN - RETURN throws_ok( $1, $2::char(5), $3, NULL ); - ELSE - RETURN throws_ok( $1, NULL, $2, $3 ); - END IF; -END; -$$ LANGUAGE plpgsql; - --- throws_ok ( query, errcode ) --- throws_ok ( query, errmsg ) -CREATE OR REPLACE FUNCTION throws_ok ( TEXT, TEXT ) -RETURNS TEXT AS $$ -BEGIN - IF octet_length($2) = 5 THEN - RETURN throws_ok( $1, $2::char(5), NULL, NULL ); - ELSE - RETURN throws_ok( $1, NULL, $2, NULL ); - END IF; -END; -$$ LANGUAGE plpgsql; - --- throws_ok ( sql ) -CREATE OR REPLACE FUNCTION throws_ok ( TEXT ) -RETURNS TEXT AS $$ - SELECT throws_ok( $1, NULL, NULL, NULL ); -$$ LANGUAGE SQL; - --- Magically cast integer error codes. --- throws_ok ( sql, errcode, errmsg, description ) -CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT throws_ok( $1, $2::char(5), $3, $4 ); -$$ LANGUAGE SQL; - --- throws_ok ( sql, errcode, errmsg ) -CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4, TEXT ) -RETURNS TEXT AS $$ - SELECT throws_ok( $1, $2::char(5), $3, NULL ); -$$ LANGUAGE SQL; - --- throws_ok ( sql, errcode ) -CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4 ) -RETURNS TEXT AS $$ - SELECT throws_ok( $1, $2::char(5), NULL, NULL ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _error_diag( TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT COALESCE( - COALESCE( NULLIF($1, '') || ': ', '' ) || COALESCE( NULLIF($2, ''), '' ), - 'NO ERROR FOUND' - ) - || COALESCE(E'\n DETAIL: ' || nullif($3, ''), '') - || COALESCE(E'\n HINT: ' || nullif($4, ''), '') - || COALESCE(E'\n SCHEMA: ' || nullif($6, ''), '') - || COALESCE(E'\n TABLE: ' || nullif($7, ''), '') - || COALESCE(E'\n COLUMN: ' || nullif($8, ''), '') - || COALESCE(E'\n CONSTRAINT: ' || nullif($9, ''), '') - || COALESCE(E'\n TYPE: ' || nullif($10, ''), '') - -- We need to manually indent all the context lines - || COALESCE(E'\n CONTEXT:\n' - || regexp_replace(NULLIF( $5, ''), '^', ' ', 'gn' - ), ''); -$$ LANGUAGE sql IMMUTABLE; - --- lives_ok( sql, description ) -CREATE OR REPLACE FUNCTION lives_ok ( TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - code TEXT := _query($1); - descr ALIAS FOR $2; - detail text; - hint text; - context text; - schname text; - tabname text; - colname text; - chkname text; - typname text; -BEGIN - EXECUTE code; - RETURN ok( TRUE, descr ); -EXCEPTION WHEN OTHERS OR ASSERT_FAILURE THEN - -- There should have been no exception. - GET STACKED DIAGNOSTICS - detail = PG_EXCEPTION_DETAIL, - hint = PG_EXCEPTION_HINT, - context = PG_EXCEPTION_CONTEXT, - schname = SCHEMA_NAME, - tabname = TABLE_NAME, - colname = COLUMN_NAME, - chkname = CONSTRAINT_NAME, - typname = PG_DATATYPE_NAME; - RETURN ok( FALSE, descr ) || E'\n' || diag( - ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) - ); -END; -$$ LANGUAGE plpgsql; - --- lives_ok( sql ) -CREATE OR REPLACE FUNCTION lives_ok ( TEXT ) -RETURNS TEXT AS $$ - SELECT lives_ok( $1, NULL ); -$$ LANGUAGE SQL; - --- performs_ok ( sql, milliseconds, description ) -CREATE OR REPLACE FUNCTION performs_ok ( TEXT, NUMERIC, TEXT ) -RETURNS TEXT AS $$ -DECLARE - query TEXT := _query($1); - max_time ALIAS FOR $2; - descr ALIAS FOR $3; - starts_at TEXT; - act_time NUMERIC; -BEGIN - starts_at := timeofday(); - EXECUTE query; - act_time := extract( millisecond from timeofday()::timestamptz - starts_at::timestamptz); - IF act_time < max_time THEN RETURN ok(TRUE, descr); END IF; - RETURN ok( FALSE, descr ) || E'\n' || diag( - ' runtime: ' || act_time || ' ms' || - E'\n exceeds: ' || max_time || ' ms' - ); -END; -$$ LANGUAGE plpgsql; - --- performs_ok ( sql, milliseconds ) -CREATE OR REPLACE FUNCTION performs_ok ( TEXT, NUMERIC ) -RETURNS TEXT AS $$ - SELECT performs_ok( - $1, $2, 'Should run in less than ' || $2 || ' ms' - ); -$$ LANGUAGE sql; - --- Convenience function to run a query many times and returns --- the middle set of those times as defined by the last argument --- e.g. _time_trials('SELECT 1', 100, 0.8) will execute 'SELECT 1' --- 100 times, and return the execution times for the middle 80 runs --- --- I could have left this logic in performs_within, but I have --- plans to hook into this function for other purposes outside --- of pgTAP -CREATE TYPE _time_trial_type -AS (a_time NUMERIC); -CREATE OR REPLACE FUNCTION _time_trials(TEXT, INT, NUMERIC) -RETURNS SETOF _time_trial_type AS $$ -DECLARE - query TEXT := _query($1); - iterations ALIAS FOR $2; - return_percent ALIAS FOR $3; - start_time TEXT; - act_time NUMERIC; - times NUMERIC[]; - offset_it INT; - limit_it INT; - offset_percent NUMERIC; - a_time _time_trial_type; -BEGIN - -- Execute the query over and over - FOR i IN 1..iterations LOOP - start_time := timeofday(); - EXECUTE query; - -- Store the execution time for the run in an array of times - times[i] := extract(millisecond from timeofday()::timestamptz - start_time::timestamptz); - END LOOP; - offset_percent := (1.0 - return_percent) / 2.0; - -- Ensure that offset skips the bottom X% of runs, or set it to 0 - SELECT GREATEST((offset_percent * iterations)::int, 0) INTO offset_it; - -- Ensure that with limit the query to returning only the middle X% of runs - SELECT GREATEST((return_percent * iterations)::int, 1) INTO limit_it; - - FOR a_time IN SELECT times[i] - FROM generate_series(array_lower(times, 1), array_upper(times, 1)) i - ORDER BY 1 - OFFSET offset_it - LIMIT limit_it LOOP - RETURN NEXT a_time; - END LOOP; -END; -$$ LANGUAGE plpgsql; - --- performs_within( sql, average_milliseconds, within, iterations, description ) -CREATE OR REPLACE FUNCTION performs_within(TEXT, NUMERIC, NUMERIC, INT, TEXT) -RETURNS TEXT AS $$ -DECLARE - query TEXT := _query($1); - expected_avg ALIAS FOR $2; - within ALIAS FOR $3; - iterations ALIAS FOR $4; - descr ALIAS FOR $5; - avg_time NUMERIC; -BEGIN - SELECT avg(a_time) FROM _time_trials(query, iterations, 0.8) t1 INTO avg_time; - IF abs(avg_time - expected_avg) < within THEN RETURN ok(TRUE, descr); END IF; - RETURN ok(FALSE, descr) || E'\n' || diag(' average runtime: ' || avg_time || ' ms' - || E'\n desired average: ' || expected_avg || ' +/- ' || within || ' ms' - ); -END; -$$ LANGUAGE plpgsql; - --- performs_within( sql, average_milliseconds, within, iterations ) -CREATE OR REPLACE FUNCTION performs_within(TEXT, NUMERIC, NUMERIC, INT) -RETURNS TEXT AS $$ -SELECT performs_within( - $1, $2, $3, $4, - 'Should run within ' || $2 || ' +/- ' || $3 || ' ms'); -$$ LANGUAGE sql; --- performs_within( sql, average_milliseconds, within, description ) -CREATE OR REPLACE FUNCTION performs_within(TEXT, NUMERIC, NUMERIC, TEXT) -RETURNS TEXT AS $$ -SELECT performs_within( - $1, $2, $3, 10, $4 - ); -$$ LANGUAGE sql; - --- performs_within( sql, average_milliseconds, within ) -CREATE OR REPLACE FUNCTION performs_within(TEXT, NUMERIC, NUMERIC) -RETURNS TEXT AS $$ -SELECT performs_within( - $1, $2, $3, 10, - 'Should run within ' || $2 || ' +/- ' || $3 || ' ms'); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _relexists ( NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - WHERE n.nspname = $1 - AND c.relname = $2 - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _relexists ( NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_class c - WHERE pg_catalog.pg_table_is_visible(c.oid) - AND c.relname = $1 - ); -$$ LANGUAGE SQL; - --- has_relation( schema, relation, description ) -CREATE OR REPLACE FUNCTION has_relation ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _relexists( $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- has_relation( relation, description ) -CREATE OR REPLACE FUNCTION has_relation ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _relexists( $1 ), $2 ); -$$ LANGUAGE SQL; - --- has_relation( relation ) -CREATE OR REPLACE FUNCTION has_relation ( NAME ) -RETURNS TEXT AS $$ - SELECT has_relation( $1, 'Relation ' || quote_ident($1) || ' should exist' ); -$$ LANGUAGE SQL; - --- hasnt_relation( schema, relation, description ) -CREATE OR REPLACE FUNCTION hasnt_relation ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _relexists( $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- hasnt_relation( relation, description ) -CREATE OR REPLACE FUNCTION hasnt_relation ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _relexists( $1 ), $2 ); -$$ LANGUAGE SQL; - --- hasnt_relation( relation ) -CREATE OR REPLACE FUNCTION hasnt_relation ( NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_relation( $1, 'Relation ' || quote_ident($1) || ' should not exist' ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _rexists ( CHAR[], NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - WHERE c.relkind = ANY($1) - AND n.nspname = $2 - AND c.relname = $3 - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _rexists ( CHAR[], NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_class c - WHERE c.relkind = ANY($1) - AND pg_catalog.pg_table_is_visible(c.oid) - AND c.relname = $2 - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _rexists ( CHAR, NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT _rexists(ARRAY[$1], $2, $3); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _rexists ( CHAR, NAME ) -RETURNS BOOLEAN AS $$ -SELECT _rexists(ARRAY[$1], $2); -$$ LANGUAGE SQL; - --- has_table( schema, table, description ) -CREATE OR REPLACE FUNCTION has_table ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists( '{r,p}'::char[], $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- has_table( schema, table ) -CREATE OR REPLACE FUNCTION has_table ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _rexists( '{r,p}'::char[], $1, $2 ), - 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' - ); -$$ LANGUAGE SQL; - --- has_table( table, description ) -CREATE OR REPLACE FUNCTION has_table ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists( '{r,p}'::char[], $1 ), $2 ); -$$ LANGUAGE SQL; - --- has_table( table ) -CREATE OR REPLACE FUNCTION has_table ( NAME ) -RETURNS TEXT AS $$ - SELECT has_table( $1, 'Table ' || quote_ident($1) || ' should exist' ); -$$ LANGUAGE SQL; - --- hasnt_table( schema, table, description ) -CREATE OR REPLACE FUNCTION hasnt_table ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _rexists( '{r,p}'::char[], $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- hasnt_table( schema, table ) -CREATE OR REPLACE FUNCTION hasnt_table ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _rexists( '{r,p}'::char[], $1, $2 ), - 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' - ); -$$ LANGUAGE SQL; - --- hasnt_table( table, description ) -CREATE OR REPLACE FUNCTION hasnt_table ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _rexists( '{r,p}'::char[], $1 ), $2 ); -$$ LANGUAGE SQL; - --- hasnt_table( table ) -CREATE OR REPLACE FUNCTION hasnt_table ( NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_table( $1, 'Table ' || quote_ident($1) || ' should not exist' ); -$$ LANGUAGE SQL; - --- has_view( schema, view, description ) -CREATE OR REPLACE FUNCTION has_view ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists( 'v', $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- has_view( schema, view ) -CREATE OR REPLACE FUNCTION has_view ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT has_view ( - $1, $2, - 'View ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' - ); -$$ LANGUAGE SQL; - --- has_view( view, description ) -CREATE OR REPLACE FUNCTION has_view ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists( 'v', $1 ), $2 ); -$$ LANGUAGE SQL; - --- has_view( view ) -CREATE OR REPLACE FUNCTION has_view ( NAME ) -RETURNS TEXT AS $$ - SELECT has_view( $1, 'View ' || quote_ident($1) || ' should exist' ); -$$ LANGUAGE SQL; - --- hasnt_view( schema, view, description ) -CREATE OR REPLACE FUNCTION hasnt_view ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _rexists( 'v', $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- hasnt_view( schema, table ) -CREATE OR REPLACE FUNCTION hasnt_view ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_view( $1, $2, - 'View ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' - ); -$$ LANGUAGE SQL; - - --- hasnt_view( view, description ) -CREATE OR REPLACE FUNCTION hasnt_view ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _rexists( 'v', $1 ), $2 ); -$$ LANGUAGE SQL; - --- hasnt_view( view ) -CREATE OR REPLACE FUNCTION hasnt_view ( NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_view( $1, 'View ' || quote_ident($1) || ' should not exist' ); -$$ LANGUAGE SQL; - --- has_sequence( schema, sequence, description ) -CREATE OR REPLACE FUNCTION has_sequence ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists( 'S', $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- has_sequence( schema, sequence ) -CREATE OR REPLACE FUNCTION has_sequence ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _rexists( 'S', $1, $2 ), - 'Sequence ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' - ); -$$ LANGUAGE SQL; - --- has_sequence( sequence, description ) -CREATE OR REPLACE FUNCTION has_sequence ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists( 'S', $1 ), $2 ); -$$ LANGUAGE SQL; - --- has_sequence( sequence ) -CREATE OR REPLACE FUNCTION has_sequence ( NAME ) -RETURNS TEXT AS $$ - SELECT has_sequence( $1, 'Sequence ' || quote_ident($1) || ' should exist' ); -$$ LANGUAGE SQL; - --- hasnt_sequence( schema, sequence, description ) -CREATE OR REPLACE FUNCTION hasnt_sequence ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _rexists( 'S', $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- hasnt_sequence( sequence, description ) -CREATE OR REPLACE FUNCTION hasnt_sequence ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _rexists( 'S', $1 ), $2 ); -$$ LANGUAGE SQL; - --- hasnt_sequence( sequence ) -CREATE OR REPLACE FUNCTION hasnt_sequence ( NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_sequence( $1, 'Sequence ' || quote_ident($1) || ' should not exist' ); -$$ LANGUAGE SQL; - --- has_foreign_table( schema, table, description ) -CREATE OR REPLACE FUNCTION has_foreign_table ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists( 'f', $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- has_foreign_table( schema, table ) -CREATE OR REPLACE FUNCTION has_foreign_table ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _rexists( 'f', $1, $2 ), - 'Foreign table ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' - ); -$$ LANGUAGE SQL; - --- has_foreign_table( table, description ) -CREATE OR REPLACE FUNCTION has_foreign_table ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists( 'f', $1 ), $2 ); -$$ LANGUAGE SQL; - --- has_foreign_table( table ) -CREATE OR REPLACE FUNCTION has_foreign_table ( NAME ) -RETURNS TEXT AS $$ - SELECT has_foreign_table( $1, 'Foreign table ' || quote_ident($1) || ' should exist' ); -$$ LANGUAGE SQL; - --- hasnt_foreign_table( schema, table, description ) -CREATE OR REPLACE FUNCTION hasnt_foreign_table ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _rexists( 'f', $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- hasnt_foreign_table( schema, table ) -CREATE OR REPLACE FUNCTION hasnt_foreign_table ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _rexists( 'f', $1, $2 ), - 'Foreign table ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' - ); -$$ LANGUAGE SQL; - --- hasnt_foreign_table( table, description ) -CREATE OR REPLACE FUNCTION hasnt_foreign_table ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _rexists( 'f', $1 ), $2 ); -$$ LANGUAGE SQL; - --- hasnt_foreign_table( table ) -CREATE OR REPLACE FUNCTION hasnt_foreign_table ( NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_foreign_table( $1, 'Foreign table ' || quote_ident($1) || ' should not exist' ); -$$ LANGUAGE SQL; - --- has_composite( schema, type, description ) -CREATE OR REPLACE FUNCTION has_composite ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists( 'c', $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- has_composite( schema, type ) -CREATE OR REPLACE FUNCTION has_composite ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT has_composite($1, $2, - 'Composite type ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' - ); -$$ LANGUAGE SQL; - --- has_composite( type, description ) -CREATE OR REPLACE FUNCTION has_composite ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists( 'c', $1 ), $2 ); -$$ LANGUAGE SQL; - --- has_composite( type ) -CREATE OR REPLACE FUNCTION has_composite ( NAME ) -RETURNS TEXT AS $$ - SELECT has_composite( $1, 'Composite type ' || quote_ident($1) || ' should exist' ); -$$ LANGUAGE SQL; - --- hasnt_composite( schema, type, description ) -CREATE OR REPLACE FUNCTION hasnt_composite ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _rexists( 'c', $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- hasnt_composite( schema, type ) -CREATE OR REPLACE FUNCTION hasnt_composite ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_composite( - $1, $2, - 'Composite type ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' - ); -$$ LANGUAGE SQL; - --- hasnt_composite( type, description ) -CREATE OR REPLACE FUNCTION hasnt_composite ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _rexists( 'c', $1 ), $2 ); -$$ LANGUAGE SQL; - --- hasnt_composite( type ) -CREATE OR REPLACE FUNCTION hasnt_composite ( NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_composite( $1, 'Composite type ' || quote_ident($1) || ' should not exist' ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _cexists ( NAME, NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid - WHERE n.nspname = $1 - AND c.relname = $2 - AND a.attnum > 0 - AND NOT a.attisdropped - AND a.attname = $3 - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _cexists ( NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_class c - JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid - WHERE c.relname = $1 - AND pg_catalog.pg_table_is_visible(c.oid) - AND a.attnum > 0 - AND NOT a.attisdropped - AND a.attname = $2 - ); -$$ LANGUAGE SQL; - --- has_column( schema, table, column, description ) -CREATE OR REPLACE FUNCTION has_column ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _cexists( $1, $2, $3 ), $4 ); -$$ LANGUAGE SQL; - --- has_column( table, column, description ) -CREATE OR REPLACE FUNCTION has_column ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _cexists( $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- has_column( table, column ) -CREATE OR REPLACE FUNCTION has_column ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT has_column( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); -$$ LANGUAGE SQL; - --- hasnt_column( schema, table, column, description ) -CREATE OR REPLACE FUNCTION hasnt_column ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _cexists( $1, $2, $3 ), $4 ); -$$ LANGUAGE SQL; - --- hasnt_column( table, column, description ) -CREATE OR REPLACE FUNCTION hasnt_column ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _cexists( $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- hasnt_column( table, column ) -CREATE OR REPLACE FUNCTION hasnt_column ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_column( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); -$$ LANGUAGE SQL; - --- _col_is_null( schema, table, column, desc, null ) -CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, NAME, TEXT, bool ) -RETURNS TEXT AS $$ -DECLARE - qcol CONSTANT text := quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3); - c_desc CONSTANT text := coalesce( - $4, - 'Column ' || qcol || ' should ' - || CASE WHEN $5 THEN 'be NOT' ELSE 'allow' END || ' NULL' - ); -BEGIN - IF NOT _cexists( $1, $2, $3 ) THEN - RETURN fail( c_desc ) || E'\n' - || diag (' Column ' || qcol || ' does not exist' ); - END IF; - RETURN ok( - EXISTS( - SELECT true - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid - WHERE n.nspname = $1 - AND c.relname = $2 - AND a.attnum > 0 - AND NOT a.attisdropped - AND a.attname = $3 - AND a.attnotnull = $5 - ), c_desc - ); -END; -$$ LANGUAGE plpgsql; - --- _col_is_null( table, column, desc, null ) -CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, TEXT, bool ) -RETURNS TEXT AS $$ -DECLARE - qcol CONSTANT text := quote_ident($1) || '.' || quote_ident($2); - c_desc CONSTANT text := coalesce( - $3, - 'Column ' || qcol || ' should ' - || CASE WHEN $4 THEN 'be NOT' ELSE 'allow' END || ' NULL' - ); -BEGIN - IF NOT _cexists( $1, $2 ) THEN - RETURN fail( c_desc ) || E'\n' - || diag (' Column ' || qcol || ' does not exist' ); - END IF; - RETURN ok( - EXISTS( - SELECT true - FROM pg_catalog.pg_class c - JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid - WHERE pg_catalog.pg_table_is_visible(c.oid) - AND c.relname = $1 - AND a.attnum > 0 - AND NOT a.attisdropped - AND a.attname = $2 - AND a.attnotnull = $4 - ), c_desc - ); -END; -$$ LANGUAGE plpgsql; - --- col_not_null( schema, table, column, description ) --- col_not_null( schema, table, column ) -CREATE OR REPLACE FUNCTION col_not_null ( schema_name NAME, table_name NAME, column_name NAME, description TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, $4, true ); -$$ LANGUAGE SQL; - --- col_not_null( table, column, description ) --- col_not_null( table, column ) -CREATE OR REPLACE FUNCTION col_not_null ( table_name NAME, column_name NAME, description TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, true ); -$$ LANGUAGE SQL; - --- col_is_null( schema, table, column, description ) --- col_is_null( schema, table, column ) -CREATE OR REPLACE FUNCTION col_is_null ( schema_name NAME, table_name NAME, column_name NAME, description TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, $4, false ); -$$ LANGUAGE SQL; - --- col_is_null( table, column, description ) --- col_is_null( table, column ) -CREATE OR REPLACE FUNCTION col_is_null ( table_name NAME, column_name NAME, description TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, false ); -$$ LANGUAGE SQL; - - -CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid - WHERE n.nspname = $1 - AND c.relname = $2 - AND a.attname = $3 - AND attnum > 0 - AND NOT a.attisdropped -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) - FROM pg_catalog.pg_attribute a - JOIN pg_catalog.pg_class c ON a.attrelid = c.oid - WHERE pg_catalog.pg_table_is_visible(c.oid) - AND c.relname = $1 - AND a.attname = $2 - AND attnum > 0 - AND NOT a.attisdropped -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_col_ns_type ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - -- Always include the namespace. - SELECT CASE WHEN pg_catalog.pg_type_is_visible(t.oid) - THEN quote_ident(tn.nspname) || '.' - ELSE '' - END || pg_catalog.format_type(a.atttypid, a.atttypmod) - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid - JOIN pg_catalog.pg_type t ON a.atttypid = t.oid - JOIN pg_catalog.pg_namespace tn ON t.typnamespace = tn.oid - WHERE n.nspname = $1 - AND c.relname = $2 - AND a.attname = $3 - AND attnum > 0 - AND NOT a.attisdropped -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _typename ( NAME ) -RETURNS TEXT AS $$ -BEGIN RETURN $1::REGTYPE; -EXCEPTION WHEN undefined_object THEN RETURN $1; -END; -$$ LANGUAGE PLPGSQL STABLE; - -CREATE OR REPLACE FUNCTION format_type_string ( TEXT ) -RETURNS TEXT AS $$ -DECLARE - want_type TEXT := $1; -BEGIN - IF pg_version_num() >= 170000 THEN - -- to_regtypemod() in 17 allows easy and corret normalization. - RETURN format_type(to_regtype(want_type), to_regtypemod(want_type)); - END IF; - - IF want_type::regtype = 'interval'::regtype THEN - -- We cannot normlize interval types without to_regtypemod(), So - -- just return it as is. - RETURN want_type; - END IF; - - -- Use the typmodin functions to correctly normalize types. - DECLARE - typmodin_arg cstring[]; - typmodin_func regproc; - typmod int; - BEGIN - -- Extract type modifier from type declaration and format as cstring[] literal. - typmodin_arg := translate(substring(want_type FROM '[(][^")]+[)]'), '()', '{}'); - - -- Find typmodin function for want_type. - SELECT typmodin INTO typmodin_func - FROM pg_catalog.pg_type - WHERE oid = want_type::regtype; - - IF typmodin_func = 0 THEN - -- Easy: types without typemods. - RETURN format_type(want_type::regtype, null); - END IF; - - -- Get typemod via type-specific typmodin function. - EXECUTE format('SELECT %s(%L)', typmodin_func, typmodin_arg) INTO typmod; - RETURN format_type(want_type::regtype, typmod); - END; - EXCEPTION WHEN OTHERS THEN RETURN NULL; -END; -$$ LANGUAGE PLPGSQL STABLE; - --- col_type_is( schema, table, column, schema, type, description ) -CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - have_type TEXT := _get_col_ns_type($1, $2, $3); - want_type TEXT; -BEGIN - IF have_type IS NULL THEN - RETURN fail( $6 ) || E'\n' || diag ( - ' Column ' || COALESCE(quote_ident($1) || '.', '') - || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' - ); - END IF; - - IF quote_ident($4) = ANY(current_schemas(true)) THEN - want_type := quote_ident($4) || '.' || format_type_string($5); - ELSE - want_type := format_type_string(quote_ident($4) || '.' || $5); - END IF; - - IF want_type IS NULL THEN - RETURN fail( $6 ) || E'\n' || diag ( - ' Type ' || quote_ident($4) || '.' || $5 || ' does not exist' - ); - END IF; - - IF have_type = want_type THEN - -- We're good to go. - RETURN ok( true, $6 ); - END IF; - - -- Wrong data type. tell 'em what we really got. - RETURN ok( false, $6 ) || E'\n' || diag( - ' have: ' || have_type || - E'\n want: ' || want_type - ); -END; -$$ LANGUAGE plpgsql; - --- col_type_is( schema, table, column, schema, type ) -CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT col_type_is( $1, $2, $3, $4, $5, 'Column ' || quote_ident($1) || '.' || quote_ident($2) - || '.' || quote_ident($3) || ' should be type ' || quote_ident($4) || '.' || $5); -$$ LANGUAGE SQL; - --- col_type_is( schema, table, column, type, description ) -CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - have_type TEXT; - want_type TEXT; -BEGIN - -- Get the data type. - IF $1 IS NULL THEN - have_type := _get_col_type($2, $3); - ELSE - have_type := _get_col_type($1, $2, $3); - END IF; - - IF have_type IS NULL THEN - RETURN fail( $5 ) || E'\n' || diag ( - ' Column ' || COALESCE(quote_ident($1) || '.', '') - || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' - ); - END IF; - - want_type := format_type_string($4); - IF want_type IS NULL THEN - RETURN fail( $5 ) || E'\n' || diag ( - ' Type ' || $4 || ' does not exist' - ); - END IF; - - IF have_type = want_type THEN - -- We're good to go. - RETURN ok( true, $5 ); - END IF; - - -- Wrong data type. tell 'em what we really got. - RETURN ok( false, $5 ) || E'\n' || diag( - ' have: ' || have_type || - E'\n want: ' || want_type - ); -END; -$$ LANGUAGE plpgsql; - --- col_type_is( schema, table, column, type ) -CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT col_type_is( $1, $2, $3, $4, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' should be type ' || $4 ); -$$ LANGUAGE SQL; - --- col_type_is( table, column, type, description ) -CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT col_type_is( NULL, $1, $2, $3, $4 ); -$$ LANGUAGE SQL; - --- col_type_is( table, column, type ) -CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT col_type_is( $1, $2, $3, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should be type ' || $3 ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _has_def ( NAME, NAME, NAME ) -RETURNS boolean AS $$ - SELECT a.atthasdef - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid - WHERE n.nspname = $1 - AND c.relname = $2 - AND a.attnum > 0 - AND NOT a.attisdropped - AND a.attname = $3 -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _has_def ( NAME, NAME ) -RETURNS boolean AS $$ - SELECT a.atthasdef - FROM pg_catalog.pg_class c - JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid - WHERE c.relname = $1 - AND a.attnum > 0 - AND NOT a.attisdropped - AND a.attname = $2 - AND pg_catalog.pg_table_is_visible(c.oid) -$$ LANGUAGE sql; - --- col_has_default( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_has_default ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -BEGIN - IF NOT _cexists( $1, $2, $3 ) THEN - RETURN fail( $4 ) || E'\n' - || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' ); - END IF; - RETURN ok( _has_def( $1, $2, $3 ), $4 ); -END -$$ LANGUAGE plpgsql; - --- col_has_default( table, column, description ) -CREATE OR REPLACE FUNCTION col_has_default ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -BEGIN - IF NOT _cexists( $1, $2 ) THEN - RETURN fail( $3 ) || E'\n' - || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' ); - END IF; - RETURN ok( _has_def( $1, $2 ), $3 ); -END; -$$ LANGUAGE plpgsql; - --- col_has_default( table, column ) -CREATE OR REPLACE FUNCTION col_has_default ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT col_has_default( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should have a default' ); -$$ LANGUAGE SQL; - --- col_hasnt_default( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_hasnt_default ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -BEGIN - IF NOT _cexists( $1, $2, $3 ) THEN - RETURN fail( $4 ) || E'\n' - || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' ); - END IF; - RETURN ok( NOT _has_def( $1, $2, $3 ), $4 ); -END; -$$ LANGUAGE plpgsql; - --- col_hasnt_default( table, column, description ) -CREATE OR REPLACE FUNCTION col_hasnt_default ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -BEGIN - IF NOT _cexists( $1, $2 ) THEN - RETURN fail( $3 ) || E'\n' - || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' ); - END IF; - RETURN ok( NOT _has_def( $1, $2 ), $3 ); -END; -$$ LANGUAGE plpgsql; - --- col_hasnt_default( table, column ) -CREATE OR REPLACE FUNCTION col_hasnt_default ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT col_hasnt_default( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should not have a default' ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ) -RETURNS TEXT AS $$ -DECLARE - thing text; -BEGIN - -- Function, cast, or special SQL syntax. - IF $1 ~ '^[^'']+[(]' OR $1 ~ '[)]::[^'']+$' OR $1 = ANY('{CURRENT_CATALOG,CURRENT_ROLE,CURRENT_SCHEMA,CURRENT_USER,SESSION_USER,USER,CURRENT_DATE,CURRENT_TIME,CURRENT_TIMESTAMP,LOCALTIME,LOCALTIMESTAMP}') THEN - RETURN is( $1, $3, $4 ); - END IF; - - EXECUTE 'SELECT is(' - || COALESCE($1, 'NULL' || '::' || $2) || '::' || $2 || ', ' - || COALESCE(quote_literal($3), 'NULL') || '::' || $2 || ', ' - || COALESCE(quote_literal($4), 'NULL') - || ')' INTO thing; - RETURN thing; -END; -$$ LANGUAGE plpgsql; - --- _cdi( schema, table, column, default, description ) -CREATE OR REPLACE FUNCTION _cdi ( NAME, NAME, NAME, anyelement, TEXT ) -RETURNS TEXT AS $$ -BEGIN - IF NOT _cexists( $1, $2, $3 ) THEN - RETURN fail( $5 ) || E'\n' - || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' ); - END IF; - - IF NOT _has_def( $1, $2, $3 ) THEN - RETURN fail( $5 ) || E'\n' - || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' has no default' ); - END IF; - - RETURN _def_is( - pg_catalog.pg_get_expr(d.adbin, d.adrelid), - pg_catalog.format_type(a.atttypid, a.atttypmod), - $4, $5 - ) - FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a, - pg_catalog.pg_attrdef d - WHERE n.oid = c.relnamespace - AND c.oid = a.attrelid - AND a.atthasdef - AND a.attrelid = d.adrelid - AND a.attnum = d.adnum - AND n.nspname = $1 - AND c.relname = $2 - AND a.attnum > 0 - AND NOT a.attisdropped - AND a.attname = $3; -END; -$$ LANGUAGE plpgsql; - --- _cdi( table, column, default, description ) -CREATE OR REPLACE FUNCTION _cdi ( NAME, NAME, anyelement, TEXT ) -RETURNS TEXT AS $$ -BEGIN - IF NOT _cexists( $1, $2 ) THEN - RETURN fail( $4 ) || E'\n' - || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' ); - END IF; - - IF NOT _has_def( $1, $2 ) THEN - RETURN fail( $4 ) || E'\n' - || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || ' has no default' ); - END IF; - - RETURN _def_is( - pg_catalog.pg_get_expr(d.adbin, d.adrelid), - pg_catalog.format_type(a.atttypid, a.atttypmod), - $3, $4 - ) - FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, pg_catalog.pg_attrdef d - WHERE c.oid = a.attrelid - AND pg_table_is_visible(c.oid) - AND a.atthasdef - AND a.attrelid = d.adrelid - AND a.attnum = d.adnum - AND c.relname = $1 - AND a.attnum > 0 - AND NOT a.attisdropped - AND a.attname = $2; -END; -$$ LANGUAGE plpgsql; - --- _cdi( table, column, default ) -CREATE OR REPLACE FUNCTION _cdi ( NAME, NAME, anyelement ) -RETURNS TEXT AS $$ - SELECT col_default_is( - $1, $2, $3, - 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should default to ' - || COALESCE( quote_literal($3), 'NULL') - ); -$$ LANGUAGE sql; - --- col_default_is( schema, table, column, default, description ) -CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, NAME, anyelement, TEXT ) -RETURNS TEXT AS $$ - SELECT _cdi( $1, $2, $3, $4, $5 ); -$$ LANGUAGE sql; - --- col_default_is( schema, table, column, default, description ) -CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _cdi( $1, $2, $3, $4, $5 ); -$$ LANGUAGE sql; - --- col_default_is( table, column, default, description ) -CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, anyelement, TEXT ) -RETURNS TEXT AS $$ - SELECT _cdi( $1, $2, $3, $4 ); -$$ LANGUAGE sql; - --- col_default_is( table, column, default, description ) -CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _cdi( $1, $2, $3, $4 ); -$$ LANGUAGE sql; - --- col_default_is( table, column, default ) -CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, anyelement ) -RETURNS TEXT AS $$ - SELECT _cdi( $1, $2, $3 ); -$$ LANGUAGE sql; - --- col_default_is( table, column, default::text ) -CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, text ) -RETURNS TEXT AS $$ - SELECT _cdi( $1, $2, $3 ); -$$ LANGUAGE sql; - --- _hasc( schema, table, constraint_type ) -CREATE OR REPLACE FUNCTION _hasc ( NAME, NAME, CHAR ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON c.relnamespace = n.oid - JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid - WHERE n.nspname = $1 - AND c.relname = $2 - AND x.contype = $3 - ); -$$ LANGUAGE sql; - --- _hasc( table, constraint_type ) -CREATE OR REPLACE FUNCTION _hasc ( NAME, CHAR ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_class c - JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid - WHERE pg_table_is_visible(c.oid) - AND c.relname = $1 - AND x.contype = $2 - ); -$$ LANGUAGE sql; - --- has_pk( schema, table, description ) -CREATE OR REPLACE FUNCTION has_pk ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _hasc( $1, $2, 'p' ), $3 ); -$$ LANGUAGE sql; - --- has_pk( schema, table ) -CREATE OR REPLACE FUNCTION has_pk ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT has_pk( $1, $2, 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have a primary key' ); -$$ LANGUAGE sql; - --- has_pk( table, description ) -CREATE OR REPLACE FUNCTION has_pk ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _hasc( $1, 'p' ), $2 ); -$$ LANGUAGE sql; - --- has_pk( table ) -CREATE OR REPLACE FUNCTION has_pk ( NAME ) -RETURNS TEXT AS $$ - SELECT has_pk( $1, 'Table ' || quote_ident($1) || ' should have a primary key' ); -$$ LANGUAGE sql; - --- hasnt_pk( schema, table, description ) -CREATE OR REPLACE FUNCTION hasnt_pk ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _hasc( $1, $2, 'p' ), $3 ); -$$ LANGUAGE sql; - --- hasnt_pk( table, description ) -CREATE OR REPLACE FUNCTION hasnt_pk ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _hasc( $1, 'p' ), $2 ); -$$ LANGUAGE sql; - --- hasnt_pk( table ) -CREATE OR REPLACE FUNCTION hasnt_pk ( NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_pk( $1, 'Table ' || quote_ident($1) || ' should not have a primary key' ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _ident_array_to_string( name[], text ) -RETURNS text AS $$ - SELECT array_to_string(ARRAY( - SELECT quote_ident($1[i]) - FROM generate_series(1, array_upper($1, 1)) s(i) - ORDER BY i - ), $2); -$$ LANGUAGE SQL immutable; - --- Borrowed from newsysviews: https://www.postgresql.org/ftp/projects/pgFoundry/newsysviews/ -CREATE OR REPLACE FUNCTION _pg_sv_column_array( OID, SMALLINT[] ) -RETURNS NAME[] AS $$ - SELECT ARRAY( - SELECT a.attname - FROM pg_catalog.pg_attribute a - JOIN generate_series(1, array_upper($2, 1)) s(i) ON a.attnum = $2[i] - WHERE attrelid = $1 - ORDER BY i - ) -$$ LANGUAGE SQL stable; - --- Borrowed from newsysviews: https://www.postgresql.org/ftp/projects/pgFoundry/newsysviews/ -CREATE OR REPLACE FUNCTION _pg_sv_table_accessible( OID, OID ) -RETURNS BOOLEAN AS $$ - SELECT CASE WHEN has_schema_privilege($1, 'USAGE') THEN ( - has_table_privilege($2, 'SELECT') - OR has_table_privilege($2, 'INSERT') - or has_table_privilege($2, 'UPDATE') - OR has_table_privilege($2, 'DELETE') - OR has_table_privilege($2, 'RULE') - OR has_table_privilege($2, 'REFERENCES') - OR has_table_privilege($2, 'TRIGGER') - ) ELSE FALSE - END; -$$ LANGUAGE SQL immutable strict; - --- Borrowed from newsysviews: https://www.postgresql.org/ftp/projects/pgFoundry/newsysviews/ -CREATE OR REPLACE VIEW pg_all_foreign_keys -AS - SELECT n1.nspname AS fk_schema_name, - c1.relname AS fk_table_name, - k1.conname AS fk_constraint_name, - c1.oid AS fk_table_oid, - _pg_sv_column_array(k1.conrelid,k1.conkey) AS fk_columns, - n2.nspname AS pk_schema_name, - c2.relname AS pk_table_name, - k2.conname AS pk_constraint_name, - c2.oid AS pk_table_oid, - ci.relname AS pk_index_name, - _pg_sv_column_array(k1.confrelid,k1.confkey) AS pk_columns, - CASE k1.confmatchtype WHEN 'f' THEN 'FULL' - WHEN 'p' THEN 'PARTIAL' - WHEN 'u' THEN 'NONE' - else null - END AS match_type, - CASE k1.confdeltype WHEN 'a' THEN 'NO ACTION' - WHEN 'c' THEN 'CASCADE' - WHEN 'd' THEN 'SET DEFAULT' - WHEN 'n' THEN 'SET NULL' - WHEN 'r' THEN 'RESTRICT' - else null - END AS on_delete, - CASE k1.confupdtype WHEN 'a' THEN 'NO ACTION' - WHEN 'c' THEN 'CASCADE' - WHEN 'd' THEN 'SET DEFAULT' - WHEN 'n' THEN 'SET NULL' - WHEN 'r' THEN 'RESTRICT' - ELSE NULL - END AS on_update, - k1.condeferrable AS is_deferrable, - k1.condeferred AS is_deferred - FROM pg_catalog.pg_constraint k1 - JOIN pg_catalog.pg_namespace n1 ON (n1.oid = k1.connamespace) - JOIN pg_catalog.pg_class c1 ON (c1.oid = k1.conrelid) - JOIN pg_catalog.pg_class c2 ON (c2.oid = k1.confrelid) - JOIN pg_catalog.pg_namespace n2 ON (n2.oid = c2.relnamespace) - JOIN pg_catalog.pg_depend d ON ( - d.classid = 'pg_constraint'::regclass - AND d.objid = k1.oid - AND d.objsubid = 0 - AND d.deptype = 'n' - AND d.refclassid = 'pg_class'::regclass - AND d.refobjsubid=0 - ) - JOIN pg_catalog.pg_class ci ON (ci.oid = d.refobjid AND ci.relkind = 'i') - LEFT JOIN pg_depend d2 ON ( - d2.classid = 'pg_class'::regclass - AND d2.objid = ci.oid - AND d2.objsubid = 0 - AND d2.deptype = 'i' - AND d2.refclassid = 'pg_constraint'::regclass - AND d2.refobjsubid = 0 - ) - LEFT JOIN pg_catalog.pg_constraint k2 ON ( - k2.oid = d2.refobjid - AND k2.contype IN ('p', 'u') - ) - WHERE k1.conrelid != 0 - AND k1.confrelid != 0 - AND k1.contype = 'f' - AND _pg_sv_table_accessible(n1.oid, c1.oid); - --- _keys( schema, table, constraint_type ) -CREATE OR REPLACE FUNCTION _keys ( NAME, NAME, CHAR ) -RETURNS SETOF NAME[] AS $$ - SELECT _pg_sv_column_array(x.conrelid,x.conkey) -- name[] doesn't support collation - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid - WHERE n.nspname = $1 - AND c.relname = $2 - AND x.contype = $3 - ORDER BY 1 -$$ LANGUAGE sql; - --- _keys( table, constraint_type ) -CREATE OR REPLACE FUNCTION _keys ( NAME, CHAR ) -RETURNS SETOF NAME[] AS $$ - SELECT _pg_sv_column_array(x.conrelid,x.conkey) -- name[] doesn't support collation - FROM pg_catalog.pg_class c - JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid - AND c.relname = $1 - AND x.contype = $2 - WHERE pg_catalog.pg_table_is_visible(c.oid) - ORDER BY 1 -$$ LANGUAGE sql; - --- _ckeys( schema, table, constraint_type ) -CREATE OR REPLACE FUNCTION _ckeys ( NAME, NAME, CHAR ) -RETURNS NAME[] AS $$ - SELECT * FROM _keys($1, $2, $3) LIMIT 1; -$$ LANGUAGE sql; - --- _ckeys( table, constraint_type ) -CREATE OR REPLACE FUNCTION _ckeys ( NAME, CHAR ) -RETURNS NAME[] AS $$ - SELECT * FROM _keys($1, $2) LIMIT 1; -$$ LANGUAGE sql; - --- col_is_pk( schema, table, column[], description ) -CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT is( _ckeys( $1, $2, 'p' ), $3, $4 ); -$$ LANGUAGE sql; - --- col_is_pk( schema, table, column[] ) -CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT col_is_pk( $1, $2, $3, 'Columns ' || quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string($3, ', ') || ') should be a primary key' ); -$$ LANGUAGE sql; - --- col_is_pk( table, column[], description ) -CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT is( _ckeys( $1, 'p' ), $2, $3 ); -$$ LANGUAGE sql; - --- col_is_pk( table, column[] ) -CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT col_is_pk( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should be a primary key' ); -$$ LANGUAGE sql; - --- col_is_pk( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT col_is_pk( $1, $2, ARRAY[$3], $4 ); -$$ LANGUAGE sql; - --- col_is_pk( schema, table, column ) -CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT col_is_pk( $1, $2, $3, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || '(' || quote_ident($3) || ') should be a primary key' ); -$$ LANGUAGE sql; - --- col_is_pk( table, column, description ) -CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT col_is_pk( $1, ARRAY[$2], $3 ); -$$ LANGUAGE sql; - --- col_is_pk( table, column ) -CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT col_is_pk( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should be a primary key' ); -$$ LANGUAGE sql; - --- col_isnt_pk( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT isnt( _ckeys( $1, $2, 'p' ), $3, $4 ); -$$ LANGUAGE sql; - --- col_isnt_pk( table, column, description ) -CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT isnt( _ckeys( $1, 'p' ), $2, $3 ); -$$ LANGUAGE sql; - --- col_isnt_pk( table, column[] ) -CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT col_isnt_pk( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should not be a primary key' ); -$$ LANGUAGE sql; - --- col_isnt_pk( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT col_isnt_pk( $1, $2, ARRAY[$3], $4 ); -$$ LANGUAGE sql; - --- col_isnt_pk( table, column, description ) -CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT col_isnt_pk( $1, ARRAY[$2], $3 ); -$$ LANGUAGE sql; - --- col_isnt_pk( table, column ) -CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT col_isnt_pk( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should not be a primary key' ); -$$ LANGUAGE sql; - --- has_fk( schema, table, description ) -CREATE OR REPLACE FUNCTION has_fk ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _hasc( $1, $2, 'f' ), $3 ); -$$ LANGUAGE sql; - --- has_fk( table, description ) -CREATE OR REPLACE FUNCTION has_fk ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _hasc( $1, 'f' ), $2 ); -$$ LANGUAGE sql; - --- has_fk( table ) -CREATE OR REPLACE FUNCTION has_fk ( NAME ) -RETURNS TEXT AS $$ - SELECT has_fk( $1, 'Table ' || quote_ident($1) || ' should have a foreign key constraint' ); -$$ LANGUAGE sql; - --- hasnt_fk( schema, table, description ) -CREATE OR REPLACE FUNCTION hasnt_fk ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _hasc( $1, $2, 'f' ), $3 ); -$$ LANGUAGE sql; - --- hasnt_fk( table, description ) -CREATE OR REPLACE FUNCTION hasnt_fk ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _hasc( $1, 'f' ), $2 ); -$$ LANGUAGE sql; - --- hasnt_fk( table ) -CREATE OR REPLACE FUNCTION hasnt_fk ( NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_fk( $1, 'Table ' || quote_ident($1) || ' should not have a foreign key constraint' ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _fkexists ( NAME, NAME, NAME[] ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT TRUE - FROM pg_all_foreign_keys - WHERE fk_schema_name = $1 - AND quote_ident(fk_table_name) = quote_ident($2) - AND fk_columns = $3 - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _fkexists ( NAME, NAME[] ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT TRUE - FROM pg_all_foreign_keys - WHERE quote_ident(fk_table_name) = quote_ident($1) - AND pg_catalog.pg_table_is_visible(fk_table_oid) - AND fk_columns = $2 - ); -$$ LANGUAGE SQL; - --- col_is_fk( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - names text[]; -BEGIN - IF _fkexists($1, $2, $3) THEN - RETURN pass( $4 ); - END IF; - - -- Try to show the columns. - SELECT ARRAY( - SELECT _ident_array_to_string(fk_columns, ', ') - FROM pg_all_foreign_keys - WHERE fk_schema_name = $1 - AND fk_table_name = $2 - ORDER BY fk_columns - ) INTO names; - - IF names[1] IS NOT NULL THEN - RETURN fail($4) || E'\n' || diag( - ' Table ' || quote_ident($1) || '.' || quote_ident($2) || E' has foreign key constraints on these columns:\n ' - || array_to_string( names, E'\n ' ) - ); - END IF; - - -- No FKs in this table. - RETURN fail($4) || E'\n' || diag( - ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' has no foreign key columns' - ); -END; -$$ LANGUAGE plpgsql; - --- col_is_fk( table, column, description ) -CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - names text[]; -BEGIN - IF _fkexists($1, $2) THEN - RETURN pass( $3 ); - END IF; - - -- Try to show the columns. - SELECT ARRAY( - SELECT _ident_array_to_string(fk_columns, ', ') - FROM pg_all_foreign_keys - WHERE fk_table_name = $1 - ORDER BY fk_columns - ) INTO names; - - IF NAMES[1] IS NOT NULL THEN - RETURN fail($3) || E'\n' || diag( - ' Table ' || quote_ident($1) || E' has foreign key constraints on these columns:\n ' - || array_to_string( names, E'\n ' ) - ); - END IF; - - -- No FKs in this table. - RETURN fail($3) || E'\n' || diag( - ' Table ' || quote_ident($1) || ' has no foreign key columns' - ); -END; -$$ LANGUAGE plpgsql; - --- col_is_fk( table, column[] ) -CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT col_is_fk( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should be a foreign key' ); -$$ LANGUAGE sql; - --- col_is_fk( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT col_is_fk( $1, $2, ARRAY[$3], $4 ); -$$ LANGUAGE sql; - --- col_is_fk( table, column, description ) -CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT col_is_fk( $1, ARRAY[$2], $3 ); -$$ LANGUAGE sql; - --- col_is_fk( table, column ) -CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT col_is_fk( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should be a foreign key' ); -$$ LANGUAGE sql; - --- col_isnt_fk( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _fkexists( $1, $2, $3 ), $4 ); -$$ LANGUAGE SQL; - --- col_isnt_fk( table, column, description ) -CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _fkexists( $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- col_isnt_fk( table, column[] ) -CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT col_isnt_fk( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should not be a foreign key' ); -$$ LANGUAGE sql; - --- col_isnt_fk( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT col_isnt_fk( $1, $2, ARRAY[$3], $4 ); -$$ LANGUAGE sql; - --- col_isnt_fk( table, column, description ) -CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT col_isnt_fk( $1, ARRAY[$2], $3 ); -$$ LANGUAGE sql; - --- col_isnt_fk( table, column ) -CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT col_isnt_fk( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should not be a foreign key' ); -$$ LANGUAGE sql; - --- has_unique( schema, table, description ) -CREATE OR REPLACE FUNCTION has_unique ( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _hasc( $1, $2, 'u' ), $3 ); -$$ LANGUAGE sql; - --- has_unique( table, description ) -CREATE OR REPLACE FUNCTION has_unique ( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _hasc( $1, 'u' ), $2 ); -$$ LANGUAGE sql; - --- has_unique( table ) -CREATE OR REPLACE FUNCTION has_unique ( TEXT ) -RETURNS TEXT AS $$ - SELECT has_unique( $1, 'Table ' || quote_ident($1) || ' should have a unique constraint' ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - akey NAME[]; - keys TEXT[] := '{}'; - have TEXT; -BEGIN - FOR akey IN SELECT * FROM _keys($1, $2, $3) LOOP - IF akey = $4 THEN RETURN pass($5); END IF; - keys = keys || akey::text; - END LOOP; - IF array_upper(keys, 0) = 1 THEN - have := 'No ' || $6 || ' constraints'; - ELSE - have := array_to_string(keys, E'\n '); - END IF; - - RETURN fail($5) || E'\n' || diag( - ' have: ' || have - || E'\n want: ' || CASE WHEN $4 IS NULL THEN 'NULL' ELSE $4::text END - ); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - akey NAME[]; - keys TEXT[] := '{}'; - have TEXT; -BEGIN - FOR akey IN SELECT * FROM _keys($1, $2) LOOP - IF akey = $3 THEN RETURN pass($4); END IF; - keys = keys || akey::text; - END LOOP; - IF array_upper(keys, 0) = 1 THEN - have := 'No ' || $5 || ' constraints'; - ELSE - have := array_to_string(keys, E'\n '); - END IF; - - RETURN fail($4) || E'\n' || diag( - ' have: ' || have - || E'\n want: ' || CASE WHEN $3 IS NULL THEN 'NULL' ELSE $3::text END - ); -END; -$$ LANGUAGE plpgsql; - --- col_is_unique( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _constraint( $1, $2, 'u', $3, $4, 'unique' ); -$$ LANGUAGE sql; - --- col_is_unique( schema, table, column[] ) -CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT col_is_unique( $1, $2, $3, 'Columns ' || quote_ident($2) || '(' || _ident_array_to_string($3, ', ') || ') should have a unique constraint' ); -$$ LANGUAGE sql; - --- col_is_unique( scheam, table, column ) -CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT col_is_unique( $1, $2, ARRAY[$3], 'Column ' || quote_ident($2) || '(' || quote_ident($3) || ') should have a unique constraint' ); -$$ LANGUAGE sql; - --- col_is_unique( table, column, description ) -CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _constraint( $1, 'u', $2, $3, 'unique' ); -$$ LANGUAGE sql; - --- col_is_unique( table, column[] ) -CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT col_is_unique( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should have a unique constraint' ); -$$ LANGUAGE sql; - --- col_is_unique( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT col_is_unique( $1, $2, ARRAY[$3], $4 ); -$$ LANGUAGE sql; - --- col_is_unique( table, column, description ) -CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT col_is_unique( $1, ARRAY[$2], $3 ); -$$ LANGUAGE sql; - --- col_is_unique( table, column ) -CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT col_is_unique( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should have a unique constraint' ); -$$ LANGUAGE sql; - --- has_check( schema, table, description ) -CREATE OR REPLACE FUNCTION has_check ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _hasc( $1, $2, 'c' ), $3 ); -$$ LANGUAGE sql; - --- has_check( table, description ) -CREATE OR REPLACE FUNCTION has_check ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _hasc( $1, 'c' ), $2 ); -$$ LANGUAGE sql; - --- has_check( table ) -CREATE OR REPLACE FUNCTION has_check ( NAME ) -RETURNS TEXT AS $$ - SELECT has_check( $1, 'Table ' || quote_ident($1) || ' should have a check constraint' ); -$$ LANGUAGE sql; - --- col_has_check( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _constraint( $1, $2, 'c', $3, $4, 'check' ); -$$ LANGUAGE sql; - --- col_has_check( table, column, description ) -CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _constraint( $1, 'c', $2, $3, 'check' ); -$$ LANGUAGE sql; - --- col_has_check( table, column[] ) -CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT col_has_check( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should have a check constraint' ); -$$ LANGUAGE sql; - --- col_has_check( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT col_has_check( $1, $2, ARRAY[$3], $4 ); -$$ LANGUAGE sql; - --- col_has_check( table, column, description ) -CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT col_has_check( $1, ARRAY[$2], $3 ); -$$ LANGUAGE sql; - --- col_has_check( table, column ) -CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT col_has_check( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should have a check constraint' ); -$$ LANGUAGE sql; - --- fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description ) -CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - sch name; - tab name; - cols name[]; -BEGIN - SELECT pk_schema_name, pk_table_name, pk_columns - FROM pg_all_foreign_keys - WHERE fk_schema_name = $1 - AND fk_table_name = $2 - AND fk_columns = $3 - INTO sch, tab, cols; - - RETURN is( - -- have - quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( $3, ', ' ) - || ') REFERENCES ' || COALESCE ( sch || '.' || tab || '(' || _ident_array_to_string( cols, ', ' ) || ')', 'NOTHING' ), - -- want - quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( $3, ', ' ) - || ') REFERENCES ' || - $4 || '.' || $5 || '(' || _ident_array_to_string( $6, ', ' ) || ')', - $7 - ); -END; -$$ LANGUAGE plpgsql; - --- fk_ok( fk_table, fk_column[], pk_table, pk_column[], description ) -CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - tab name; - cols name[]; -BEGIN - SELECT pk_table_name, pk_columns - FROM pg_all_foreign_keys - WHERE fk_table_name = $1 - AND fk_columns = $2 - AND pg_catalog.pg_table_is_visible(fk_table_oid) - INTO tab, cols; - - RETURN is( - -- have - $1 || '(' || _ident_array_to_string( $2, ', ' ) - || ') REFERENCES ' || COALESCE( tab || '(' || _ident_array_to_string( cols, ', ' ) || ')', 'NOTHING'), - -- want - $1 || '(' || _ident_array_to_string( $2, ', ' ) - || ') REFERENCES ' || - $3 || '(' || _ident_array_to_string( $4, ', ' ) || ')', - $5 - ); -END; -$$ LANGUAGE plpgsql; - --- fk_ok( fk_schema, fk_table, fk_column[], fk_schema, pk_table, pk_column[] ) -CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT fk_ok( $1, $2, $3, $4, $5, $6, - quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( $3, ', ' ) - || ') should reference ' || - $4 || '.' || $5 || '(' || _ident_array_to_string( $6, ', ' ) || ')' - ); -$$ LANGUAGE sql; - --- fk_ok( fk_table, fk_column[], pk_table, pk_column[] ) -CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT fk_ok( $1, $2, $3, $4, - $1 || '(' || _ident_array_to_string( $2, ', ' ) - || ') should reference ' || - $3 || '(' || _ident_array_to_string( $4, ', ' ) || ')' - ); -$$ LANGUAGE sql; - --- fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column, description ) -CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT fk_ok( $1, $2, ARRAY[$3], $4, $5, ARRAY[$6], $7 ); -$$ LANGUAGE sql; - --- fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column ) -CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT fk_ok( $1, $2, ARRAY[$3], $4, $5, ARRAY[$6] ); -$$ LANGUAGE sql; - --- fk_ok( fk_table, fk_column, pk_table, pk_column, description ) -CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT fk_ok( $1, ARRAY[$2], $3, ARRAY[$4], $5 ); -$$ LANGUAGE sql; - --- fk_ok( fk_table, fk_column, pk_table, pk_column ) -CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT fk_ok( $1, ARRAY[$2], $3, ARRAY[$4] ); -$$ LANGUAGE sql; - -/* - * tap_funky used to just be a simple view, but the problem with that is the - * definition of pg_proc changed in version 11. Thanks to how pg_dump (and - * hence pg_upgrade) works, this made it impossible to upgrade Postgres if - * pgTap was installed. In order to fix that, we need code that will actually - * work on both < PG11 and >= PG11. - */ -CREATE OR REPLACE FUNCTION _prokind( p_oid oid ) -RETURNS "char" AS $$ -BEGIN - IF pg_version_num() >= 110000 THEN - RETURN prokind FROM pg_catalog.pg_proc WHERE oid = p_oid; - ELSE - RETURN CASE WHEN proisagg THEN 'a' WHEN proiswindow THEN 'w' ELSE 'f' END - FROM pg_catalog.pg_proc WHERE oid = p_oid; - END IF; -END; -$$ LANGUAGE plpgsql STABLE; - -CREATE OR REPLACE VIEW tap_funky - AS SELECT p.oid AS oid, - n.nspname AS schema, - p.proname AS name, - pg_catalog.pg_get_userbyid(p.proowner) AS owner, - array_to_string(p.proargtypes::regtype[], ',') AS args, - CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END - || p.prorettype::regtype AS returns, - p.prolang AS langoid, - p.proisstrict AS is_strict, - _prokind(p.oid) AS kind, - p.prosecdef AS is_definer, - p.proretset AS returns_set, - p.provolatile::char AS volatility, - pg_catalog.pg_function_is_visible(p.oid) AS is_visible - FROM pg_catalog.pg_proc p - JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid -; - -CREATE OR REPLACE FUNCTION _funkargs ( NAME[] ) -RETURNS TEXT AS $$ -BEGIN - RETURN array_to_string($1::regtype[], ','); -EXCEPTION WHEN undefined_object THEN - RETURN array_to_string($1, ','); -END; -$$ LANGUAGE PLPGSQL STABLE; - -CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME, NAME[] ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT TRUE - FROM tap_funky - WHERE schema = $1 - AND name = $2 - AND args = _funkargs($3) - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( SELECT TRUE FROM tap_funky WHERE schema = $1 AND name = $2 ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME[] ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT TRUE - FROM tap_funky - WHERE name = $1 - AND args = _funkargs($2) - AND is_visible - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _got_func ( NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( SELECT TRUE FROM tap_funky WHERE name = $1 AND is_visible); -$$ LANGUAGE SQL; - --- has_function( schema, function, args[], description ) -CREATE OR REPLACE FUNCTION has_function ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _got_func($1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- has_function( schema, function, args[] ) -CREATE OR REPLACE FUNCTION has_function( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - _got_func($1, $2, $3), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should exist' - ); -$$ LANGUAGE sql; - --- has_function( schema, function, description ) -CREATE OR REPLACE FUNCTION has_function ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _got_func($1, $2), $3 ); -$$ LANGUAGE SQL; - --- has_function( schema, function ) -CREATE OR REPLACE FUNCTION has_function( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _got_func($1, $2), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should exist' - ); -$$ LANGUAGE sql; - --- has_function( function, args[], description ) -CREATE OR REPLACE FUNCTION has_function ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _got_func($1, $2), $3 ); -$$ LANGUAGE SQL; - --- has_function( function, args[] ) -CREATE OR REPLACE FUNCTION has_function( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - _got_func($1, $2), - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should exist' - ); -$$ LANGUAGE sql; - --- has_function( function, description ) -CREATE OR REPLACE FUNCTION has_function( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _got_func($1), $2 ); -$$ LANGUAGE sql; - --- has_function( function ) -CREATE OR REPLACE FUNCTION has_function( NAME ) -RETURNS TEXT AS $$ - SELECT ok( _got_func($1), 'Function ' || quote_ident($1) || '() should exist' ); -$$ LANGUAGE sql; - --- hasnt_function( schema, function, args[], description ) -CREATE OR REPLACE FUNCTION hasnt_function ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _got_func($1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- hasnt_function( schema, function, args[] ) -CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _got_func($1, $2, $3), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should not exist' - ); -$$ LANGUAGE sql; - --- hasnt_function( schema, function, description ) -CREATE OR REPLACE FUNCTION hasnt_function ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _got_func($1, $2), $3 ); -$$ LANGUAGE SQL; - --- hasnt_function( schema, function ) -CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _got_func($1, $2), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not exist' - ); -$$ LANGUAGE sql; - --- hasnt_function( function, args[], description ) -CREATE OR REPLACE FUNCTION hasnt_function ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _got_func($1, $2), $3 ); -$$ LANGUAGE SQL; - --- hasnt_function( function, args[] ) -CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _got_func($1, $2), - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should not exist' - ); -$$ LANGUAGE sql; - --- hasnt_function( function, description ) -CREATE OR REPLACE FUNCTION hasnt_function( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _got_func($1), $2 ); -$$ LANGUAGE sql; - --- hasnt_function( function ) -CREATE OR REPLACE FUNCTION hasnt_function( NAME ) -RETURNS TEXT AS $$ - SELECT ok( NOT _got_func($1), 'Function ' || quote_ident($1) || '() should not exist' ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _pg_sv_type_array( OID[] ) -RETURNS NAME[] AS $$ - SELECT ARRAY( - SELECT t.typname - FROM pg_catalog.pg_type t - JOIN generate_series(1, array_upper($1, 1)) s(i) ON t.oid = $1[i] - ORDER BY i - ) -$$ LANGUAGE SQL stable; - --- can( schema, functions[], description ) -CREATE OR REPLACE FUNCTION can ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - missing text[]; -BEGIN - SELECT ARRAY( - SELECT quote_ident($2[i]) - FROM generate_series(1, array_upper($2, 1)) s(i) - LEFT JOIN tap_funky ON name = $2[i] AND schema = $1 - WHERE oid IS NULL - GROUP BY $2[i], s.i - ORDER BY MIN(s.i) - ) INTO missing; - IF missing[1] IS NULL THEN - RETURN ok( true, $3 ); - END IF; - RETURN ok( false, $3 ) || E'\n' || diag( - ' ' || quote_ident($1) || '.' || - array_to_string( missing, E'() missing\n ' || quote_ident($1) || '.') || - '() missing' - ); -END; -$$ LANGUAGE plpgsql; - --- can( schema, functions[] ) -CREATE OR REPLACE FUNCTION can ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT can( $1, $2, 'Schema ' || quote_ident($1) || ' can' ); -$$ LANGUAGE sql; - --- can( functions[], description ) -CREATE OR REPLACE FUNCTION can ( NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - missing text[]; -BEGIN - SELECT ARRAY( - SELECT quote_ident($1[i]) - FROM generate_series(1, array_upper($1, 1)) s(i) - LEFT JOIN pg_catalog.pg_proc p - ON $1[i] = p.proname - AND pg_catalog.pg_function_is_visible(p.oid) - WHERE p.oid IS NULL - ORDER BY s.i - ) INTO missing; - IF missing[1] IS NULL THEN - RETURN ok( true, $2 ); - END IF; - RETURN ok( false, $2 ) || E'\n' || diag( - ' ' || - array_to_string( missing, E'() missing\n ') || - '() missing' - ); -END; -$$ LANGUAGE plpgsql; - --- can( functions[] ) -CREATE OR REPLACE FUNCTION can ( NAME[] ) -RETURNS TEXT AS $$ - SELECT can( $1, 'Schema ' || _ident_array_to_string(current_schemas(true), ' or ') || ' can' ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _ikeys( NAME, NAME, NAME) -RETURNS TEXT[] AS $$ - SELECT ARRAY( - SELECT pg_catalog.pg_get_indexdef( ci.oid, s.i + 1, false) - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace - JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) - ON x.indkey[s.i] IS NOT NULL - WHERE ct.relname = $2 - AND ci.relname = $3 - AND n.nspname = $1 - ORDER BY s.i - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _ikeys( NAME, NAME) -RETURNS TEXT[] AS $$ - SELECT ARRAY( - SELECT pg_catalog.pg_get_indexdef( ci.oid, s.i + 1, false) - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) - ON x.indkey[s.i] IS NOT NULL - WHERE ct.relname = $1 - AND ci.relname = $2 - AND pg_catalog.pg_table_is_visible(ct.oid) - ORDER BY s.i - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _have_index( NAME, NAME, NAME) -RETURNS BOOLEAN AS $$ - SELECT EXISTS ( - SELECT TRUE - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace - WHERE n.nspname = $1 - AND ct.relname = $2 - AND ci.relname = $3 - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _have_index( NAME, NAME) -RETURNS BOOLEAN AS $$ - SELECT EXISTS ( - SELECT TRUE - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - WHERE ct.relname = $1 - AND ci.relname = $2 - AND pg_catalog.pg_table_is_visible(ct.oid) - ); -$$ LANGUAGE sql; - --- has_index( schema, table, index, columns[], description ) -CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME[], text ) -RETURNS TEXT AS $$ -DECLARE - index_cols name[]; -BEGIN - index_cols := _ikeys($1, $2, $3 ); - - IF index_cols IS NULL OR index_cols = '{}'::name[] THEN - RETURN ok( false, $5 ) || E'\n' - || diag( 'Index ' || quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || ' not found'); - END IF; - - RETURN is( - quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || array_to_string( index_cols, ', ' ) || ')', - quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || array_to_string( $4, ', ' ) || ')', - $5 - ); -END; -$$ LANGUAGE plpgsql; - --- has_index( schema, table, index, columns[] ) -CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT has_index( $1, $2, $3, $4, 'Index ' || quote_ident($3) || ' should exist' ); -$$ LANGUAGE sql; - --- has_index( schema, table, index, column, description ) -CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME, text ) -RETURNS TEXT AS $$ - SELECT has_index( $1, $2, $3, ARRAY[$4], $5 ); -$$ LANGUAGE sql; - --- has_index( schema, table, index, column ) -CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT has_index( $1, $2, $3, $4, 'Index ' || quote_ident($3) || ' should exist' ); -$$ LANGUAGE sql; - --- has_index( table, index, columns[], description ) -CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME[], text ) -RETURNS TEXT AS $$ -DECLARE - index_cols name[]; -BEGIN - index_cols := _ikeys($1, $2 ); - - IF index_cols IS NULL OR index_cols = '{}'::name[] THEN - RETURN ok( false, $4 ) || E'\n' - || diag( 'Index ' || quote_ident($2) || ' ON ' || quote_ident($1) || ' not found'); - END IF; - - RETURN is( - quote_ident($2) || ' ON ' || quote_ident($1) || '(' || array_to_string( index_cols, ', ' ) || ')', - quote_ident($2) || ' ON ' || quote_ident($1) || '(' || array_to_string( $3, ', ' ) || ')', - $4 - ); -END; -$$ LANGUAGE plpgsql; - --- has_index( table, index, columns[] ) -CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT has_index( $1, $2, $3, 'Index ' || quote_ident($2) || ' should exist' ); -$$ LANGUAGE sql; - --- _is_schema( schema ) -CREATE OR REPLACE FUNCTION _is_schema( NAME ) -returns boolean AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_namespace - WHERE nspname = $1 - ); -$$ LANGUAGE sql; - --- has_index( table, index, column/expression, description ) --- has_index( schema, table, index, column/expression ) -CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, text ) -RETURNS TEXT AS $$ - SELECT CASE WHEN _is_schema( $1 ) THEN - -- Looking for schema.table index. - ok ( _have_index( $1, $2, $3 ), $4) - ELSE - -- Looking for particular columns. - has_index( $1, $2, ARRAY[$3], $4 ) - END; -$$ LANGUAGE sql; - --- has_index( table, index, column/expression ) --- has_index( schema, table, index ) -CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ -BEGIN - IF _is_schema($1) THEN - -- ( schema, table, index ) - RETURN ok( _have_index( $1, $2, $3 ), 'Index ' || quote_ident($3) || ' should exist' ); - ELSE - -- ( table, index, column/expression ) - RETURN has_index( $1, $2, $3, 'Index ' || quote_ident($2) || ' should exist' ); - END IF; -END; -$$ LANGUAGE plpgsql; - --- has_index( table, index, description ) -CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, text ) -RETURNS TEXT AS $$ - SELECT CASE WHEN $3 LIKE '%(%' - THEN has_index( $1, $2, $3::name ) - ELSE ok( _have_index( $1, $2 ), $3 ) - END; -$$ LANGUAGE sql; - --- has_index( table, index ) -CREATE OR REPLACE FUNCTION has_index ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( _have_index( $1, $2 ), 'Index ' || quote_ident($2) || ' should exist' ); -$$ LANGUAGE sql; - --- hasnt_index( schema, table, index, description ) -CREATE OR REPLACE FUNCTION hasnt_index ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -BEGIN - RETURN ok( NOT _have_index( $1, $2, $3 ), $4 ); -END; -$$ LANGUAGE plpgSQL; - --- hasnt_index( schema, table, index ) -CREATE OR REPLACE FUNCTION hasnt_index ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _have_index( $1, $2, $3 ), - 'Index ' || quote_ident($3) || ' should not exist' - ); -$$ LANGUAGE SQL; - --- hasnt_index( table, index, description ) -CREATE OR REPLACE FUNCTION hasnt_index ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _have_index( $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- hasnt_index( table, index ) -CREATE OR REPLACE FUNCTION hasnt_index ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _have_index( $1, $2 ), - 'Index ' || quote_ident($2) || ' should not exist' - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _is_indexed( NAME, NAME, TEXT[] ) -RETURNS BOOL AS $$ -SELECT EXISTS( SELECT TRUE FROM ( - SELECT _ikeys(coalesce($1, n.nspname), $2, ci.relname) AS cols - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace - WHERE ($1 IS NULL OR n.nspname = $1) - AND ct.relname = $2 - ) icols - WHERE cols = $3 ) -$$ LANGUAGE sql; - --- is_indexed( schema, table, columns[], description ) -CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _is_indexed($1, $2, $3), $4 ); -$$ LANGUAGE sql; - --- is_indexed( schema, table, columns[] ) -CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - _is_indexed($1, $2, $3), - 'Should have an index on ' || quote_ident($1) || '.' || quote_ident($2) || '(' || array_to_string( $3, ', ' ) || ')' - ); -$$ LANGUAGE sql; - --- is_indexed( table, columns[], description ) -CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _is_indexed(NULL, $1, $2), $3 ); -$$ LANGUAGE sql; - --- is_indexed( table, columns[] ) -CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - _is_indexed(NULL, $1, $2), - 'Should have an index on ' || quote_ident($1) || '(' || array_to_string( $2, ', ' ) || ')' - ); -$$ LANGUAGE sql; - --- is_indexed( schema, table, column, description ) -CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok ( _is_indexed( $1, $2, ARRAY[$3]::NAME[]), $4); -$$ LANGUAGE sql; - --- is_indexed( schema, table, column ) --- is_indexed( table, column, description ) -CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT CASE WHEN _is_schema( $1 ) THEN - -- Looking for schema.table index. - is_indexed( $1, $2, ARRAY[$3]::NAME[] ) - ELSE - -- Looking for particular columns. - is_indexed( $1, ARRAY[$2]::NAME[], $3 ) - END; -$$ LANGUAGE sql; - --- is_indexed( table, column ) -CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok ( _is_indexed( NULL, $1, ARRAY[$2]::NAME[]) ); -$$ LANGUAGE sql; - --- index_is_unique( schema, table, index, description ) -CREATE OR REPLACE FUNCTION index_is_unique ( NAME, NAME, NAME, text ) -RETURNS TEXT AS $$ -DECLARE - res boolean; -BEGIN - SELECT x.indisunique - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace - WHERE ct.relname = $2 - AND ci.relname = $3 - AND n.nspname = $1 - INTO res; - - RETURN ok( COALESCE(res, false), $4 ); -END; -$$ LANGUAGE plpgsql; - --- index_is_unique( schema, table, index ) -CREATE OR REPLACE FUNCTION index_is_unique ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT index_is_unique( - $1, $2, $3, - 'Index ' || quote_ident($3) || ' should be unique' - ); -$$ LANGUAGE sql; - --- index_is_unique( table, index ) -CREATE OR REPLACE FUNCTION index_is_unique ( NAME, NAME ) -RETURNS TEXT AS $$ -DECLARE - res boolean; -BEGIN - SELECT x.indisunique - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - WHERE ct.relname = $1 - AND ci.relname = $2 - AND pg_catalog.pg_table_is_visible(ct.oid) - INTO res; - - RETURN ok( - COALESCE(res, false), - 'Index ' || quote_ident($2) || ' should be unique' - ); -END; -$$ LANGUAGE plpgsql; - --- index_is_unique( index ) -CREATE OR REPLACE FUNCTION index_is_unique ( NAME ) -RETURNS TEXT AS $$ -DECLARE - res boolean; -BEGIN - SELECT x.indisunique - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - WHERE ci.relname = $1 - AND pg_catalog.pg_table_is_visible(ct.oid) - INTO res; - - RETURN ok( - COALESCE(res, false), - 'Index ' || quote_ident($1) || ' should be unique' - ); -END; -$$ LANGUAGE plpgsql; - --- index_is_primary( schema, table, index, description ) -CREATE OR REPLACE FUNCTION index_is_primary ( NAME, NAME, NAME, text ) -RETURNS TEXT AS $$ -DECLARE - res boolean; -BEGIN - SELECT x.indisprimary - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace - WHERE ct.relname = $2 - AND ci.relname = $3 - AND n.nspname = $1 - INTO res; - - RETURN ok( COALESCE(res, false), $4 ); -END; -$$ LANGUAGE plpgsql; - --- index_is_primary( schema, table, index ) -CREATE OR REPLACE FUNCTION index_is_primary ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT index_is_primary( - $1, $2, $3, - 'Index ' || quote_ident($3) || ' should be on a primary key' - ); -$$ LANGUAGE sql; - --- index_is_primary( table, index ) -CREATE OR REPLACE FUNCTION index_is_primary ( NAME, NAME ) -RETURNS TEXT AS $$ -DECLARE - res boolean; -BEGIN - SELECT x.indisprimary - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - WHERE ct.relname = $1 - AND ci.relname = $2 - AND pg_catalog.pg_table_is_visible(ct.oid) - INTO res; - - RETURN ok( - COALESCE(res, false), - 'Index ' || quote_ident($2) || ' should be on a primary key' - ); -END; -$$ LANGUAGE plpgsql; - --- index_is_primary( index ) -CREATE OR REPLACE FUNCTION index_is_primary ( NAME ) -RETURNS TEXT AS $$ -DECLARE - res boolean; -BEGIN - SELECT x.indisprimary - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - WHERE ci.relname = $1 - AND pg_catalog.pg_table_is_visible(ct.oid) - INTO res; - - RETURN ok( - COALESCE(res, false), - 'Index ' || quote_ident($1) || ' should be on a primary key' - ); -END; -$$ LANGUAGE plpgsql; - --- is_clustered( schema, table, index, description ) -CREATE OR REPLACE FUNCTION is_clustered ( NAME, NAME, NAME, text ) -RETURNS TEXT AS $$ -DECLARE - res boolean; -BEGIN - SELECT x.indisclustered - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace - WHERE ct.relname = $2 - AND ci.relname = $3 - AND n.nspname = $1 - INTO res; - - RETURN ok( COALESCE(res, false), $4 ); -END; -$$ LANGUAGE plpgsql; - --- is_clustered( schema, table, index ) -CREATE OR REPLACE FUNCTION is_clustered ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT is_clustered( - $1, $2, $3, - 'Table ' || quote_ident($1) || '.' || quote_ident($2) || - ' should be clustered on index ' || quote_ident($3) - ); -$$ LANGUAGE sql; - --- is_clustered( table, index ) -CREATE OR REPLACE FUNCTION is_clustered ( NAME, NAME ) -RETURNS TEXT AS $$ -DECLARE - res boolean; -BEGIN - SELECT x.indisclustered - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - WHERE ct.relname = $1 - AND ci.relname = $2 - INTO res; - - RETURN ok( - COALESCE(res, false), - 'Table ' || quote_ident($1) || ' should be clustered on index ' || quote_ident($2) - ); -END; -$$ LANGUAGE plpgsql; - --- is_clustered( index ) -CREATE OR REPLACE FUNCTION is_clustered ( NAME ) -RETURNS TEXT AS $$ -DECLARE - res boolean; -BEGIN - SELECT x.indisclustered - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - WHERE ci.relname = $1 - INTO res; - - RETURN ok( - COALESCE(res, false), - 'Table should be clustered on index ' || quote_ident($1) - ); -END; -$$ LANGUAGE plpgsql; - --- index_is_type( schema, table, index, type, description ) -CREATE OR REPLACE FUNCTION index_is_type ( NAME, NAME, NAME, NAME, text ) -RETURNS TEXT AS $$ -DECLARE - aname name; -BEGIN - SELECT am.amname - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace - JOIN pg_catalog.pg_am am ON ci.relam = am.oid - WHERE ct.relname = $2 - AND ci.relname = $3 - AND n.nspname = $1 - INTO aname; - - return is( aname, $4, $5 ); -END; -$$ LANGUAGE plpgsql; - --- index_is_type( schema, table, index, type ) -CREATE OR REPLACE FUNCTION index_is_type ( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT index_is_type( - $1, $2, $3, $4, - 'Index ' || quote_ident($3) || ' should be a ' || quote_ident($4) || ' index' - ); -$$ LANGUAGE SQL; - --- index_is_type( table, index, type ) -CREATE OR REPLACE FUNCTION index_is_type ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ -DECLARE - aname name; -BEGIN - SELECT am.amname - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN pg_catalog.pg_am am ON ci.relam = am.oid - WHERE ct.relname = $1 - AND ci.relname = $2 - INTO aname; - - return is( - aname, $3, - 'Index ' || quote_ident($2) || ' should be a ' || quote_ident($3) || ' index' - ); -END; -$$ LANGUAGE plpgsql; - --- index_is_type( index, type ) -CREATE OR REPLACE FUNCTION index_is_type ( NAME, NAME ) -RETURNS TEXT AS $$ -DECLARE - aname name; -BEGIN - SELECT am.amname - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN pg_catalog.pg_am am ON ci.relam = am.oid - WHERE ci.relname = $1 - INTO aname; - - return is( - aname, $2, - 'Index ' || quote_ident($1) || ' should be a ' || quote_ident($2) || ' index' - ); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION _trig ( NAME, NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_trigger t - JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid - JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE n.nspname = $1 - AND c.relname = $2 - AND t.tgname = $3 - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _trig ( NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_trigger t - JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid - WHERE c.relname = $1 - AND t.tgname = $2 - AND pg_catalog.pg_table_is_visible(c.oid) - ); -$$ LANGUAGE SQL; - --- has_trigger( schema, table, trigger, description ) -CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _trig($1, $2, $3), $4); -$$ LANGUAGE SQL; - --- has_trigger( schema, table, trigger ) -CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT has_trigger( - $1, $2, $3, - 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have trigger ' || quote_ident($3) - ); -$$ LANGUAGE sql; - --- has_trigger( table, trigger, description ) -CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _trig($1, $2), $3); -$$ LANGUAGE sql; - --- has_trigger( table, trigger ) -CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( _trig($1, $2), 'Table ' || quote_ident($1) || ' should have trigger ' || quote_ident($2)); -$$ LANGUAGE SQL; - --- hasnt_trigger( schema, table, trigger, description ) -CREATE OR REPLACE FUNCTION hasnt_trigger ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _trig($1, $2, $3), $4); -$$ LANGUAGE SQL; - --- hasnt_trigger( schema, table, trigger ) -CREATE OR REPLACE FUNCTION hasnt_trigger ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _trig($1, $2, $3), - 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should not have trigger ' || quote_ident($3) - ); -$$ LANGUAGE sql; - --- hasnt_trigger( table, trigger, description ) -CREATE OR REPLACE FUNCTION hasnt_trigger ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _trig($1, $2), $3); -$$ LANGUAGE sql; - --- hasnt_trigger( table, trigger ) -CREATE OR REPLACE FUNCTION hasnt_trigger ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( NOT _trig($1, $2), 'Table ' || quote_ident($1) || ' should not have trigger ' || quote_ident($2)); -$$ LANGUAGE SQL; - --- trigger_is( schema, table, trigger, schema, function, description ) -CREATE OR REPLACE FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME, text ) -RETURNS TEXT AS $$ -DECLARE - pname text; -BEGIN - SELECT quote_ident(ni.nspname) || '.' || quote_ident(p.proname) - FROM pg_catalog.pg_trigger t - JOIN pg_catalog.pg_class ct ON ct.oid = t.tgrelid - JOIN pg_catalog.pg_namespace nt ON nt.oid = ct.relnamespace - JOIN pg_catalog.pg_proc p ON p.oid = t.tgfoid - JOIN pg_catalog.pg_namespace ni ON ni.oid = p.pronamespace - WHERE nt.nspname = $1 - AND ct.relname = $2 - AND t.tgname = $3 - INTO pname; - - RETURN is( pname, quote_ident($4) || '.' || quote_ident($5), $6 ); -END; -$$ LANGUAGE plpgsql; - --- trigger_is( schema, table, trigger, schema, function ) -CREATE OR REPLACE FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT trigger_is( - $1, $2, $3, $4, $5, - 'Trigger ' || quote_ident($3) || ' should call ' || quote_ident($4) || '.' || quote_ident($5) || '()' - ); -$$ LANGUAGE sql; - --- trigger_is( table, trigger, function, description ) -CREATE OR REPLACE FUNCTION trigger_is ( NAME, NAME, NAME, text ) -RETURNS TEXT AS $$ -DECLARE - pname text; -BEGIN - SELECT p.proname - FROM pg_catalog.pg_trigger t - JOIN pg_catalog.pg_class ct ON ct.oid = t.tgrelid - JOIN pg_catalog.pg_proc p ON p.oid = t.tgfoid - WHERE ct.relname = $1 - AND t.tgname = $2 - AND pg_catalog.pg_table_is_visible(ct.oid) - INTO pname; - - RETURN is( pname, $3::text, $4 ); -END; -$$ LANGUAGE plpgsql; - --- trigger_is( table, trigger, function ) -CREATE OR REPLACE FUNCTION trigger_is ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT trigger_is( - $1, $2, $3, - 'Trigger ' || quote_ident($2) || ' should call ' || quote_ident($3) || '()' - ); -$$ LANGUAGE sql; - --- has_schema( schema, description ) -CREATE OR REPLACE FUNCTION has_schema( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( - EXISTS( - SELECT true - FROM pg_catalog.pg_namespace - WHERE nspname = $1 - ), $2 - ); -$$ LANGUAGE sql; - --- has_schema( schema ) -CREATE OR REPLACE FUNCTION has_schema( NAME ) -RETURNS TEXT AS $$ - SELECT has_schema( $1, 'Schema ' || quote_ident($1) || ' should exist' ); -$$ LANGUAGE sql; - --- hasnt_schema( schema, description ) -CREATE OR REPLACE FUNCTION hasnt_schema( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( - NOT EXISTS( - SELECT true - FROM pg_catalog.pg_namespace - WHERE nspname = $1 - ), $2 - ); -$$ LANGUAGE sql; - --- hasnt_schema( schema ) -CREATE OR REPLACE FUNCTION hasnt_schema( NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_schema( $1, 'Schema ' || quote_ident($1) || ' should not exist' ); -$$ LANGUAGE sql; - --- has_tablespace( tablespace, location, description ) -CREATE OR REPLACE FUNCTION has_tablespace( NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ -BEGIN - IF pg_version_num() >= 90200 THEN - RETURN ok( - EXISTS( - SELECT true - FROM pg_catalog.pg_tablespace - WHERE spcname = $1 - AND pg_tablespace_location(oid) = $2 - ), $3 - ); - ELSE - RETURN ok( - EXISTS( - SELECT true - FROM pg_catalog.pg_tablespace - WHERE spcname = $1 - AND spclocation = $2 - ), $3 - ); - END IF; -END; -$$ LANGUAGE plpgsql; - --- has_tablespace( tablespace, description ) -CREATE OR REPLACE FUNCTION has_tablespace( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( - EXISTS( - SELECT true - FROM pg_catalog.pg_tablespace - WHERE spcname = $1 - ), $2 - ); -$$ LANGUAGE sql; - --- has_tablespace( tablespace ) -CREATE OR REPLACE FUNCTION has_tablespace( NAME ) -RETURNS TEXT AS $$ - SELECT has_tablespace( $1, 'Tablespace ' || quote_ident($1) || ' should exist' ); -$$ LANGUAGE sql; - --- hasnt_tablespace( tablespace, description ) -CREATE OR REPLACE FUNCTION hasnt_tablespace( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( - NOT EXISTS( - SELECT true - FROM pg_catalog.pg_tablespace - WHERE spcname = $1 - ), $2 - ); -$$ LANGUAGE sql; - --- hasnt_tablespace( tablespace ) -CREATE OR REPLACE FUNCTION hasnt_tablespace( NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_tablespace( $1, 'Tablespace ' || quote_ident($1) || ' should not exist' ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _has_type( NAME, NAME, CHAR[] ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_type t - JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid - WHERE t.typisdefined - AND n.nspname = $1 - AND t.typname = $2 - AND t.typtype = ANY( COALESCE($3, ARRAY['b', 'c', 'd', 'p', 'e']) ) - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _has_type( NAME, CHAR[] ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_type t - WHERE t.typisdefined - AND pg_catalog.pg_type_is_visible(t.oid) - AND t.typname = $1 - AND t.typtype = ANY( COALESCE($2, ARRAY['b', 'c', 'd', 'p', 'e']) ) - ); -$$ LANGUAGE sql; - --- has_type( schema, type, description ) -CREATE OR REPLACE FUNCTION has_type( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _has_type( $1, $2, NULL ), $3 ); -$$ LANGUAGE sql; - --- has_type( schema, type ) -CREATE OR REPLACE FUNCTION has_type( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT has_type( $1, $2, 'Type ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); -$$ LANGUAGE sql; - --- has_type( type, description ) -CREATE OR REPLACE FUNCTION has_type( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _has_type( $1, NULL ), $2 ); -$$ LANGUAGE sql; - --- has_type( type ) -CREATE OR REPLACE FUNCTION has_type( NAME ) -RETURNS TEXT AS $$ - SELECT ok( _has_type( $1, NULL ), ('Type ' || quote_ident($1) || ' should exist')::text ); -$$ LANGUAGE sql; - --- hasnt_type( schema, type, description ) -CREATE OR REPLACE FUNCTION hasnt_type( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _has_type( $1, $2, NULL ), $3 ); -$$ LANGUAGE sql; - --- hasnt_type( schema, type ) -CREATE OR REPLACE FUNCTION hasnt_type( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_type( $1, $2, 'Type ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); -$$ LANGUAGE sql; - --- hasnt_type( type, description ) -CREATE OR REPLACE FUNCTION hasnt_type( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _has_type( $1, NULL ), $2 ); -$$ LANGUAGE sql; - --- hasnt_type( type ) -CREATE OR REPLACE FUNCTION hasnt_type( NAME ) -RETURNS TEXT AS $$ - SELECT ok( NOT _has_type( $1, NULL ), ('Type ' || quote_ident($1) || ' should not exist')::text ); -$$ LANGUAGE sql; - --- has_domain( schema, domain, description ) -CREATE OR REPLACE FUNCTION has_domain( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _has_type( $1, $2, ARRAY['d'] ), $3 ); -$$ LANGUAGE sql; - --- has_domain( schema, domain ) -CREATE OR REPLACE FUNCTION has_domain( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT has_domain( $1, $2, 'Domain ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); -$$ LANGUAGE sql; - --- has_domain( domain, description ) -CREATE OR REPLACE FUNCTION has_domain( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _has_type( $1, ARRAY['d'] ), $2 ); -$$ LANGUAGE sql; - --- has_domain( domain ) -CREATE OR REPLACE FUNCTION has_domain( NAME ) -RETURNS TEXT AS $$ - SELECT ok( _has_type( $1, ARRAY['d'] ), ('Domain ' || quote_ident($1) || ' should exist')::text ); -$$ LANGUAGE sql; - --- hasnt_domain( schema, domain, description ) -CREATE OR REPLACE FUNCTION hasnt_domain( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _has_type( $1, $2, ARRAY['d'] ), $3 ); -$$ LANGUAGE sql; - --- hasnt_domain( schema, domain ) -CREATE OR REPLACE FUNCTION hasnt_domain( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_domain( $1, $2, 'Domain ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); -$$ LANGUAGE sql; - --- hasnt_domain( domain, description ) -CREATE OR REPLACE FUNCTION hasnt_domain( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _has_type( $1, ARRAY['d'] ), $2 ); -$$ LANGUAGE sql; - --- hasnt_domain( domain ) -CREATE OR REPLACE FUNCTION hasnt_domain( NAME ) -RETURNS TEXT AS $$ - SELECT ok( NOT _has_type( $1, ARRAY['d'] ), ('Domain ' || quote_ident($1) || ' should not exist')::text ); -$$ LANGUAGE sql; - --- has_enum( schema, enum, description ) -CREATE OR REPLACE FUNCTION has_enum( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _has_type( $1, $2, ARRAY['e'] ), $3 ); -$$ LANGUAGE sql; - --- has_enum( schema, enum ) -CREATE OR REPLACE FUNCTION has_enum( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT has_enum( $1, $2, 'Enum ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); -$$ LANGUAGE sql; - --- has_enum( enum, description ) -CREATE OR REPLACE FUNCTION has_enum( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _has_type( $1, ARRAY['e'] ), $2 ); -$$ LANGUAGE sql; - --- has_enum( enum ) -CREATE OR REPLACE FUNCTION has_enum( NAME ) -RETURNS TEXT AS $$ - SELECT ok( _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should exist')::text ); -$$ LANGUAGE sql; - --- hasnt_enum( schema, enum, description ) -CREATE OR REPLACE FUNCTION hasnt_enum( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _has_type( $1, $2, ARRAY['e'] ), $3 ); -$$ LANGUAGE sql; - --- hasnt_enum( schema, enum ) -CREATE OR REPLACE FUNCTION hasnt_enum( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_enum( $1, $2, 'Enum ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); -$$ LANGUAGE sql; - --- hasnt_enum( enum, description ) -CREATE OR REPLACE FUNCTION hasnt_enum( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _has_type( $1, ARRAY['e'] ), $2 ); -$$ LANGUAGE sql; - --- hasnt_enum( enum ) -CREATE OR REPLACE FUNCTION hasnt_enum( NAME ) -RETURNS TEXT AS $$ - SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); -$$ LANGUAGE sql; - --- enum_has_labels( schema, enum, labels, description ) -CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT is( - ARRAY( - SELECT e.enumlabel - FROM pg_catalog.pg_type t - JOIN pg_catalog.pg_enum e ON t.oid = e.enumtypid - JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid - WHERE t.typisdefined - AND n.nspname = $1 - AND t.typname = $2 - AND t.typtype = 'e' - ORDER BY e.enumsortorder - ), - $3, - $4 - ); -$$ LANGUAGE sql; - --- enum_has_labels( schema, enum, labels ) -CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT enum_has_labels( - $1, $2, $3, - 'Enum ' || quote_ident($1) || '.' || quote_ident($2) || ' should have labels (' || array_to_string( $3, ', ' ) || ')' - ); -$$ LANGUAGE sql; - --- enum_has_labels( enum, labels, description ) -CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT is( - ARRAY( - SELECT e.enumlabel - FROM pg_catalog.pg_type t - JOIN pg_catalog.pg_enum e ON t.oid = e.enumtypid - WHERE t.typisdefined - AND pg_catalog.pg_type_is_visible(t.oid) - AND t.typname = $1 - AND t.typtype = 'e' - ORDER BY e.enumsortorder - ), - $2, - $3 - ); -$$ LANGUAGE sql; - --- enum_has_labels( enum, labels ) -CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT enum_has_labels( - $1, $2, - 'Enum ' || quote_ident($1) || ' should have labels (' || array_to_string( $2, ', ' ) || ')' - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _has_role( NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_roles - WHERE rolname = $1 - ); -$$ LANGUAGE sql STRICT; - --- has_role( role, description ) -CREATE OR REPLACE FUNCTION has_role( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _has_role($1), $2 ); -$$ LANGUAGE sql; - --- has_role( role ) -CREATE OR REPLACE FUNCTION has_role( NAME ) -RETURNS TEXT AS $$ - SELECT ok( _has_role($1), 'Role ' || quote_ident($1) || ' should exist' ); -$$ LANGUAGE sql; - --- hasnt_role( role, description ) -CREATE OR REPLACE FUNCTION hasnt_role( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _has_role($1), $2 ); -$$ LANGUAGE sql; - --- hasnt_role( role ) -CREATE OR REPLACE FUNCTION hasnt_role( NAME ) -RETURNS TEXT AS $$ - SELECT ok( NOT _has_role($1), 'Role ' || quote_ident($1) || ' should not exist' ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _has_user( NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( SELECT true FROM pg_catalog.pg_user WHERE usename = $1); -$$ LANGUAGE sql STRICT; - --- has_user( user, description ) -CREATE OR REPLACE FUNCTION has_user( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _has_user($1), $2 ); -$$ LANGUAGE sql; - --- has_user( user ) -CREATE OR REPLACE FUNCTION has_user( NAME ) -RETURNS TEXT AS $$ - SELECT ok( _has_user( $1 ), 'User ' || quote_ident($1) || ' should exist'); -$$ LANGUAGE sql; - --- hasnt_user( user, description ) -CREATE OR REPLACE FUNCTION hasnt_user( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _has_user($1), $2 ); -$$ LANGUAGE sql; - --- hasnt_user( user ) -CREATE OR REPLACE FUNCTION hasnt_user( NAME ) -RETURNS TEXT AS $$ - SELECT ok( NOT _has_user( $1 ), 'User ' || quote_ident($1) || ' should not exist'); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _is_super( NAME ) -RETURNS BOOLEAN AS $$ - SELECT rolsuper - FROM pg_catalog.pg_roles - WHERE rolname = $1 -$$ LANGUAGE sql STRICT; - --- is_superuser( user, description ) -CREATE OR REPLACE FUNCTION is_superuser( NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - is_super boolean := _is_super($1); -BEGIN - IF is_super IS NULL THEN - RETURN fail( $2 ) || E'\n' || diag( ' User ' || quote_ident($1) || ' does not exist') ; - END IF; - RETURN ok( is_super, $2 ); -END; -$$ LANGUAGE plpgsql; - --- is_superuser( user ) -CREATE OR REPLACE FUNCTION is_superuser( NAME ) -RETURNS TEXT AS $$ - SELECT is_superuser( $1, 'User ' || quote_ident($1) || ' should be a super user' ); -$$ LANGUAGE sql; - --- isnt_superuser( user, description ) -CREATE OR REPLACE FUNCTION isnt_superuser( NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - is_super boolean := _is_super($1); -BEGIN - IF is_super IS NULL THEN - RETURN fail( $2 ) || E'\n' || diag( ' User ' || quote_ident($1) || ' does not exist') ; - END IF; - RETURN ok( NOT is_super, $2 ); -END; -$$ LANGUAGE plpgsql; - --- isnt_superuser( user ) -CREATE OR REPLACE FUNCTION isnt_superuser( NAME ) -RETURNS TEXT AS $$ - SELECT isnt_superuser( $1, 'User ' || quote_ident($1) || ' should not be a super user' ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _has_group( NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_group - WHERE groname = $1 - ); -$$ LANGUAGE sql STRICT; - --- has_group( group, description ) -CREATE OR REPLACE FUNCTION has_group( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _has_group($1), $2 ); -$$ LANGUAGE sql; - --- has_group( group ) -CREATE OR REPLACE FUNCTION has_group( NAME ) -RETURNS TEXT AS $$ - SELECT ok( _has_group($1), 'Group ' || quote_ident($1) || ' should exist' ); -$$ LANGUAGE sql; - --- hasnt_group( group, description ) -CREATE OR REPLACE FUNCTION hasnt_group( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _has_group($1), $2 ); -$$ LANGUAGE sql; - --- hasnt_group( group ) -CREATE OR REPLACE FUNCTION hasnt_group( NAME ) -RETURNS TEXT AS $$ - SELECT ok( NOT _has_group($1), 'Group ' || quote_ident($1) || ' should not exist' ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _grolist ( NAME ) -RETURNS oid[] AS $$ - SELECT ARRAY( - SELECT member - FROM pg_catalog.pg_auth_members m - JOIN pg_catalog.pg_roles r ON m.roleid = r.oid - WHERE r.rolname = $1 - ); -$$ LANGUAGE sql; - --- is_member_of( role, members[], description ) -CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - missing text[]; -BEGIN - IF NOT _has_role($1) THEN - RETURN fail( $3 ) || E'\n' || diag ( - ' Role ' || quote_ident($1) || ' does not exist' - ); - END IF; - - SELECT ARRAY( - SELECT quote_ident($2[i]) - FROM generate_series(1, array_upper($2, 1)) s(i) - LEFT JOIN pg_catalog.pg_roles r ON rolname = $2[i] - WHERE r.oid IS NULL - OR NOT r.oid = ANY ( _grolist($1) ) - ORDER BY s.i - ) INTO missing; - IF missing[1] IS NULL THEN - RETURN ok( true, $3 ); - END IF; - RETURN ok( false, $3 ) || E'\n' || diag( - ' Members missing from the ' || quote_ident($1) || E' role:\n ' || - array_to_string( missing, E'\n ') - ); -END; -$$ LANGUAGE plpgsql; - --- is_member_of( role, member, description ) -CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT is_member_of( $1, ARRAY[$2], $3 ); -$$ LANGUAGE SQL; - --- is_member_of( role, members[] ) -CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT is_member_of( $1, $2, 'Should have members of role ' || quote_ident($1) ); -$$ LANGUAGE SQL; - --- is_member_of( role, member ) -CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT is_member_of( $1, ARRAY[$2] ); -$$ LANGUAGE SQL; - --- isnt_member_of( role, members[], description ) -CREATE OR REPLACE FUNCTION isnt_member_of( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - extra text[]; -BEGIN - IF NOT _has_role($1) THEN - RETURN fail( $3 ) || E'\n' || diag ( - ' Role ' || quote_ident($1) || ' does not exist' - ); - END IF; - - SELECT ARRAY( - SELECT quote_ident($2[i]) - FROM generate_series(1, array_upper($2, 1)) s(i) - LEFT JOIN pg_catalog.pg_roles r ON rolname = $2[i] - WHERE r.oid = ANY ( _grolist($1) ) - ORDER BY s.i - ) INTO extra; - IF extra[1] IS NULL THEN - RETURN ok( true, $3 ); - END IF; - RETURN ok( false, $3 ) || E'\n' || diag( - ' Members, who should not be in ' || quote_ident($1) || E' role:\n ' || - array_to_string( extra, E'\n ') - ); -END; -$$ LANGUAGE plpgsql; - --- isnt_member_of( role, member, description ) -CREATE OR REPLACE FUNCTION isnt_member_of( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT isnt_member_of( $1, ARRAY[$2], $3 ); -$$ LANGUAGE SQL; - --- isnt_member_of( role, members[] ) -CREATE OR REPLACE FUNCTION isnt_member_of( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT isnt_member_of( $1, $2, 'Should not have members of role ' || quote_ident($1) ); -$$ LANGUAGE SQL; - --- isnt_member_of( role, member ) -CREATE OR REPLACE FUNCTION isnt_member_of( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT isnt_member_of( $1, ARRAY[$2] ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _cmp_types(oid, name) -RETURNS BOOLEAN AS $$ - SELECT pg_catalog.format_type($1, NULL) = _typename($2); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _cast_exists ( NAME, NAME, NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS ( - SELECT TRUE - FROM pg_catalog.pg_cast c - JOIN pg_catalog.pg_proc p ON c.castfunc = p.oid - JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid - WHERE _cmp_types(castsource, $1) - AND _cmp_types(casttarget, $2) - AND n.nspname = $3 - AND p.proname = $4 - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _cast_exists ( NAME, NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS ( - SELECT TRUE - FROM pg_catalog.pg_cast c - JOIN pg_catalog.pg_proc p ON c.castfunc = p.oid - WHERE _cmp_types(castsource, $1) - AND _cmp_types(casttarget, $2) - AND p.proname = $3 - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _cast_exists ( NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS ( - SELECT TRUE - FROM pg_catalog.pg_cast c - WHERE _cmp_types(castsource, $1) - AND _cmp_types(casttarget, $2) - ); -$$ LANGUAGE SQL; - --- has_cast( source_type, target_type, schema, function, description ) -CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _cast_exists( $1, $2, $3, $4 ), $5 ); -$$ LANGUAGE SQL; - --- has_cast( source_type, target_type, schema, function ) -CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _cast_exists( $1, $2, $3, $4 ), - 'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2) - || ') WITH FUNCTION ' || quote_ident($3) - || '.' || quote_ident($4) || '() should exist' - ); -$$ LANGUAGE SQL; - --- has_cast( source_type, target_type, function, description ) -CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _cast_exists( $1, $2, $3 ), $4 ); -$$ LANGUAGE SQL; - --- has_cast( source_type, target_type, function ) -CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _cast_exists( $1, $2, $3 ), - 'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2) - || ') WITH FUNCTION ' || quote_ident($3) || '() should exist' - ); -$$ LANGUAGE SQL; - --- has_cast( source_type, target_type, description ) -CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _cast_exists( $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- has_cast( source_type, target_type ) -CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _cast_exists( $1, $2 ), - 'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2) - || ') should exist' - ); -$$ LANGUAGE SQL; - --- hasnt_cast( source_type, target_type, schema, function, description ) -CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _cast_exists( $1, $2, $3, $4 ), $5 ); -$$ LANGUAGE SQL; - --- hasnt_cast( source_type, target_type, schema, function ) -CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _cast_exists( $1, $2, $3, $4 ), - 'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2) - || ') WITH FUNCTION ' || quote_ident($3) - || '.' || quote_ident($4) || '() should not exist' - ); -$$ LANGUAGE SQL; - --- hasnt_cast( source_type, target_type, function, description ) -CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _cast_exists( $1, $2, $3 ), $4 ); -$$ LANGUAGE SQL; - --- hasnt_cast( source_type, target_type, function ) -CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _cast_exists( $1, $2, $3 ), - 'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2) - || ') WITH FUNCTION ' || quote_ident($3) || '() should not exist' - ); -$$ LANGUAGE SQL; - --- hasnt_cast( source_type, target_type, description ) -CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _cast_exists( $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- hasnt_cast( source_type, target_type ) -CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _cast_exists( $1, $2 ), - 'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2) - || ') should not exist' - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _expand_context( char ) -RETURNS text AS $$ - SELECT CASE $1 - WHEN 'i' THEN 'implicit' - WHEN 'a' THEN 'assignment' - WHEN 'e' THEN 'explicit' - ELSE 'unknown' END -$$ LANGUAGE SQL IMMUTABLE; - -CREATE OR REPLACE FUNCTION _get_context( NAME, NAME ) -RETURNS "char" AS $$ - SELECT c.castcontext - FROM pg_catalog.pg_cast c - WHERE _cmp_types(castsource, $1) - AND _cmp_types(casttarget, $2) -$$ LANGUAGE SQL; - --- cast_context_is( source_type, target_type, context, description ) -CREATE OR REPLACE FUNCTION cast_context_is( NAME, NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - want char = substring(LOWER($3) FROM 1 FOR 1); - have char := _get_context($1, $2); -BEGIN - IF have IS NOT NULL THEN - RETURN is( _expand_context(have), _expand_context(want), $4 ); - END IF; - - RETURN ok( false, $4 ) || E'\n' || diag( - ' Cast (' || quote_ident($1) || ' AS ' || quote_ident($2) - || ') does not exist' - ); -END; -$$ LANGUAGE plpgsql; - --- cast_context_is( source_type, target_type, context ) -CREATE OR REPLACE FUNCTION cast_context_is( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT cast_context_is( - $1, $2, $3, - 'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2) - || ') context should be ' || _expand_context(substring(LOWER($3) FROM 1 FOR 1)) - ); -$$ LANGUAGE SQL; - --- _op_exists( left_type, schema, name, right_type, return_type ) -CREATE OR REPLACE FUNCTION _op_exists ( NAME, NAME, NAME, NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS ( - SELECT TRUE - FROM pg_catalog.pg_operator o - JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid - WHERE n.nspname = $2 - AND o.oprname = $3 - AND CASE o.oprkind WHEN 'l' THEN $1 IS NULL - ELSE _cmp_types(o.oprleft, _typename($1)) END - AND CASE o.oprkind WHEN 'r' THEN $4 IS NULL - ELSE _cmp_types(o.oprright, _typename($4)) END - AND _cmp_types(o.oprresult, $5) - ); -$$ LANGUAGE SQL; - --- _op_exists( left_type, name, right_type, return_type ) -CREATE OR REPLACE FUNCTION _op_exists ( NAME, NAME, NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS ( - SELECT TRUE - FROM pg_catalog.pg_operator o - WHERE pg_catalog.pg_operator_is_visible(o.oid) - AND o.oprname = $2 - AND CASE o.oprkind WHEN 'l' THEN $1 IS NULL - ELSE _cmp_types(o.oprleft, _typename($1)) END - AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL - ELSE _cmp_types(o.oprright, _typename($3)) END - AND _cmp_types(o.oprresult, $4) - ); -$$ LANGUAGE SQL; - --- _op_exists( left_type, name, right_type ) -CREATE OR REPLACE FUNCTION _op_exists ( NAME, NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS ( - SELECT TRUE - FROM pg_catalog.pg_operator o - WHERE pg_catalog.pg_operator_is_visible(o.oid) - AND o.oprname = $2 - AND CASE o.oprkind WHEN 'l' THEN $1 IS NULL - ELSE _cmp_types(o.oprleft, _typename($1)) END - AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL - ELSE _cmp_types(o.oprright, _typename($3)) END - ); -$$ LANGUAGE SQL; - --- has_operator( left_type, schema, name, right_type, return_type, description ) -CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _op_exists($1, $2, $3, $4, $5 ), $6 ); -$$ LANGUAGE SQL; - --- has_operator( left_type, schema, name, right_type, return_type ) -CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _op_exists($1, $2, $3, $4, $5 ), - 'Operator ' || quote_ident($2) || '.' || $3 || '(' || $1 || ',' || $4 - || ') RETURNS ' || $5 || ' should exist' - ); -$$ LANGUAGE SQL; - --- has_operator( left_type, name, right_type, return_type, description ) -CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _op_exists($1, $2, $3, $4 ), $5 ); -$$ LANGUAGE SQL; - --- has_operator( left_type, name, right_type, return_type ) -CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _op_exists($1, $2, $3, $4 ), - 'Operator ' || $2 || '(' || $1 || ',' || $3 - || ') RETURNS ' || $4 || ' should exist' - ); -$$ LANGUAGE SQL; - --- has_operator( left_type, name, right_type, description ) -CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _op_exists($1, $2, $3 ), $4 ); -$$ LANGUAGE SQL; - --- has_operator( left_type, name, right_type ) -CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _op_exists($1, $2, $3 ), - 'Operator ' || $2 || '(' || $1 || ',' || $3 - || ') should exist' - ); -$$ LANGUAGE SQL; - --- hasnt_operator( left_type, schema, name, right_type, return_type, description ) -CREATE OR REPLACE FUNCTION hasnt_operator ( NAME, NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _op_exists($1, $2, $3, $4, $5 ), $6 ); -$$ LANGUAGE SQL; - --- hasnt_operator( left_type, schema, name, right_type, return_type ) -CREATE OR REPLACE FUNCTION hasnt_operator ( NAME, NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _op_exists($1, $2, $3, $4, $5 ), - 'Operator ' || quote_ident($2) || '.' || $3 || '(' || $1 || ',' || $4 - || ') RETURNS ' || $5 || ' should not exist' - ); -$$ LANGUAGE SQL; - --- hasnt_operator( left_type, name, right_type, return_type, description ) -CREATE OR REPLACE FUNCTION hasnt_operator ( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _op_exists($1, $2, $3, $4 ), $5 ); -$$ LANGUAGE SQL; - --- hasnt_operator( left_type, name, right_type, return_type ) -CREATE OR REPLACE FUNCTION hasnt_operator ( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _op_exists($1, $2, $3, $4 ), - 'Operator ' || $2 || '(' || $1 || ',' || $3 - || ') RETURNS ' || $4 || ' should not exist' - ); -$$ LANGUAGE SQL; - --- hasnt_operator( left_type, name, right_type, description ) -CREATE OR REPLACE FUNCTION hasnt_operator ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _op_exists($1, $2, $3 ), $4 ); -$$ LANGUAGE SQL; - --- hasnt_operator( left_type, name, right_type ) -CREATE OR REPLACE FUNCTION hasnt_operator ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _op_exists($1, $2, $3 ), - 'Operator ' || $2 || '(' || $1 || ',' || $3 - || ') should not exist' - ); -$$ LANGUAGE SQL; - --- has_leftop( schema, name, right_type, return_type, description ) -CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _op_exists(NULL, $1, $2, $3, $4), $5 ); -$$ LANGUAGE SQL; - --- has_leftop( schema, name, right_type, return_type ) -CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _op_exists(NULL, $1, $2, $3, $4 ), - 'Left operator ' || quote_ident($1) || '.' || $2 || '(NONE,' - || $3 || ') RETURNS ' || $4 || ' should exist' - ); -$$ LANGUAGE SQL; - --- has_leftop( name, right_type, return_type, description ) -CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _op_exists(NULL, $1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- has_leftop( name, right_type, return_type ) -CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _op_exists(NULL, $1, $2, $3 ), - 'Left operator ' || $1 || '(NONE,' || $2 || ') RETURNS ' || $3 || ' should exist' - ); -$$ LANGUAGE SQL; - --- has_leftop( name, right_type, description ) -CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _op_exists(NULL, $1, $2), $3 ); -$$ LANGUAGE SQL; - --- has_leftop( name, right_type ) -CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _op_exists(NULL, $1, $2 ), - 'Left operator ' || $1 || '(NONE,' || $2 || ') should exist' - ); -$$ LANGUAGE SQL; - --- hasnt_leftop( schema, name, right_type, return_type, description ) -CREATE OR REPLACE FUNCTION hasnt_leftop ( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _op_exists(NULL, $1, $2, $3, $4), $5 ); -$$ LANGUAGE SQL; - --- hasnt_leftop( schema, name, right_type, return_type ) -CREATE OR REPLACE FUNCTION hasnt_leftop ( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _op_exists(NULL, $1, $2, $3, $4 ), - 'Left operator ' || quote_ident($1) || '.' || $2 || '(NONE,' - || $3 || ') RETURNS ' || $4 || ' should not exist' - ); -$$ LANGUAGE SQL; - --- hasnt_leftop( name, right_type, return_type, description ) -CREATE OR REPLACE FUNCTION hasnt_leftop ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _op_exists(NULL, $1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- hasnt_leftop( name, right_type, return_type ) -CREATE OR REPLACE FUNCTION hasnt_leftop ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _op_exists(NULL, $1, $2, $3 ), - 'Left operator ' || $1 || '(NONE,' || $2 || ') RETURNS ' || $3 || ' should not exist' - ); -$$ LANGUAGE SQL; - --- hasnt_leftop( name, right_type, description ) -CREATE OR REPLACE FUNCTION hasnt_leftop ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _op_exists(NULL, $1, $2), $3 ); -$$ LANGUAGE SQL; - --- hasnt_leftop( name, right_type ) -CREATE OR REPLACE FUNCTION hasnt_leftop ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _op_exists(NULL, $1, $2 ), - 'Left operator ' || $1 || '(NONE,' || $2 || ') should not exist' - ); -$$ LANGUAGE SQL; - --- has_rightop( left_type, schema, name, return_type, description ) -CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _op_exists( $1, $2, $3, NULL, $4), $5 ); -$$ LANGUAGE SQL; - --- has_rightop( left_type, schema, name, return_type ) -CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _op_exists($1, $2, $3, NULL, $4 ), - 'Right operator ' || quote_ident($2) || '.' || $3 || '(' - || $1 || ',NONE) RETURNS ' || $4 || ' should exist' - ); -$$ LANGUAGE SQL; - --- has_rightop( left_type, name, return_type, description ) -CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _op_exists( $1, $2, NULL, $3), $4 ); -$$ LANGUAGE SQL; - --- has_rightop( left_type, name, return_type ) -CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _op_exists($1, $2, NULL, $3 ), - 'Right operator ' || $2 || '(' - || $1 || ',NONE) RETURNS ' || $3 || ' should exist' - ); -$$ LANGUAGE SQL; - --- has_rightop( left_type, name, description ) -CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _op_exists( $1, $2, NULL), $3 ); -$$ LANGUAGE SQL; - --- has_rightop( left_type, name ) -CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _op_exists($1, $2, NULL ), - 'Right operator ' || $2 || '(' || $1 || ',NONE) should exist' - ); -$$ LANGUAGE SQL; - --- hasnt_rightop( left_type, schema, name, return_type, description ) -CREATE OR REPLACE FUNCTION hasnt_rightop ( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _op_exists( $1, $2, $3, NULL, $4), $5 ); -$$ LANGUAGE SQL; - --- hasnt_rightop( left_type, schema, name, return_type ) -CREATE OR REPLACE FUNCTION hasnt_rightop ( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _op_exists($1, $2, $3, NULL, $4 ), - 'Right operator ' || quote_ident($2) || '.' || $3 || '(' - || $1 || ',NONE) RETURNS ' || $4 || ' should not exist' - ); -$$ LANGUAGE SQL; - --- hasnt_rightop( left_type, name, return_type, description ) -CREATE OR REPLACE FUNCTION hasnt_rightop ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _op_exists( $1, $2, NULL, $3), $4 ); -$$ LANGUAGE SQL; - --- hasnt_rightop( left_type, name, return_type ) -CREATE OR REPLACE FUNCTION hasnt_rightop ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _op_exists($1, $2, NULL, $3 ), - 'Right operator ' || $2 || '(' - || $1 || ',NONE) RETURNS ' || $3 || ' should not exist' - ); -$$ LANGUAGE SQL; - --- hasnt_rightop( left_type, name, description ) -CREATE OR REPLACE FUNCTION hasnt_rightop ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _op_exists( $1, $2, NULL), $3 ); -$$ LANGUAGE SQL; - --- hasnt_rightop( left_type, name ) -CREATE OR REPLACE FUNCTION hasnt_rightop ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _op_exists($1, $2, NULL ), - 'Right operator ' || $2 || '(' || $1 || ',NONE) should not exist' - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _are ( text, name[], name[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - what ALIAS FOR $1; - extras ALIAS FOR $2; - missing ALIAS FOR $3; - descr ALIAS FOR $4; - msg TEXT := ''; - res BOOLEAN := TRUE; -BEGIN - IF extras[1] IS NOT NULL THEN - res = FALSE; - msg := E'\n' || diag( - ' Extra ' || what || E':\n ' - || _ident_array_to_sorted_string( extras, E'\n ' ) - ); - END IF; - IF missing[1] IS NOT NULL THEN - res = FALSE; - msg := msg || E'\n' || diag( - ' Missing ' || what || E':\n ' - || _ident_array_to_sorted_string( missing, E'\n ' ) - ); - END IF; - - RETURN ok(res, descr) || msg; -END; -$$ LANGUAGE plpgsql; - --- tablespaces_are( tablespaces, description ) -CREATE OR REPLACE FUNCTION tablespaces_are ( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'tablespaces', - ARRAY( - SELECT spcname - FROM pg_catalog.pg_tablespace - EXCEPT - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - ), - ARRAY( - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - EXCEPT - SELECT spcname - FROM pg_catalog.pg_tablespace - ), - $2 - ); -$$ LANGUAGE SQL; - --- tablespaces_are( tablespaces ) -CREATE OR REPLACE FUNCTION tablespaces_are ( NAME[] ) -RETURNS TEXT AS $$ - SELECT tablespaces_are( $1, 'There should be the correct tablespaces' ); -$$ LANGUAGE SQL; - --- schemas_are( schemas, description ) -CREATE OR REPLACE FUNCTION schemas_are ( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'schemas', - ARRAY( - SELECT nspname - FROM pg_catalog.pg_namespace - WHERE nspname NOT LIKE 'pg\_%' - AND nspname <> 'information_schema' - EXCEPT - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - ), - ARRAY( - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - EXCEPT - SELECT nspname - FROM pg_catalog.pg_namespace - WHERE nspname NOT LIKE 'pg\_%' - AND nspname <> 'information_schema' - ), - $2 - ); -$$ LANGUAGE SQL; - --- schemas_are( schemas ) -CREATE OR REPLACE FUNCTION schemas_are ( NAME[] ) -RETURNS TEXT AS $$ - SELECT schemas_are( $1, 'There should be the correct schemas' ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _extras ( CHAR[], NAME, NAME[] ) -RETURNS NAME[] AS $$ - SELECT ARRAY( - SELECT c.relname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - WHERE c.relkind = ANY($1) - AND n.nspname = $2 - AND c.relname NOT IN('pg_all_foreign_keys', 'tap_funky', '__tresults___numb_seq', '__tcache___id_seq') - EXCEPT - SELECT $3[i] - FROM generate_series(1, array_upper($3, 1)) s(i) - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _extras ( CHAR[], NAME[] ) -RETURNS NAME[] AS $$ - SELECT ARRAY( - SELECT c.relname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - WHERE pg_catalog.pg_table_is_visible(c.oid) - AND n.nspname <> 'pg_catalog' - AND c.relkind = ANY($1) - AND c.relname NOT IN ('__tcache__', 'pg_all_foreign_keys', 'tap_funky', '__tresults___numb_seq', '__tcache___id_seq') - EXCEPT - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _extras ( CHAR, NAME, NAME[] ) -RETURNS NAME[] AS $$ - SELECT _extras(ARRAY[$1], $2, $3); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _extras ( CHAR, NAME[] ) -RETURNS NAME[] AS $$ -SELECT _extras(ARRAY[$1], $2); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _missing ( CHAR[], NAME, NAME[] ) -RETURNS NAME[] AS $$ - SELECT ARRAY( - SELECT $3[i] - FROM generate_series(1, array_upper($3, 1)) s(i) - EXCEPT - SELECT c.relname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - WHERE c.relkind = ANY($1) - AND n.nspname = $2 - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _missing ( CHAR[], NAME[] ) -RETURNS NAME[] AS $$ - SELECT ARRAY( - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - EXCEPT - SELECT c.relname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - WHERE pg_catalog.pg_table_is_visible(c.oid) - AND n.nspname NOT IN ('pg_catalog', 'information_schema') - AND c.relkind = ANY($1) - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _missing ( CHAR, NAME, NAME[] ) -RETURNS NAME[] AS $$ - SELECT _missing(ARRAY[$1], $2, $3); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _missing ( CHAR, NAME[] ) -RETURNS NAME[] AS $$ - SELECT _missing(ARRAY[$1], $2); -$$ LANGUAGE SQL; - --- tables_are( schema, tables, description ) -CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( 'tables', _extras('{r,p}'::char[], $1, $2), _missing('{r,p}'::char[], $1, $2), $3); -$$ LANGUAGE SQL; - --- tables_are( tables, description ) -CREATE OR REPLACE FUNCTION tables_are ( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( 'tables', _extras('{r,p}'::char[], $1), _missing('{r,p}'::char[], $1), $2); -$$ LANGUAGE SQL; - --- tables_are( schema, tables ) -CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _are( - 'tables', _extras('{r,p}'::char[], $1, $2), _missing('{r,p}'::char[], $1, $2), - 'Schema ' || quote_ident($1) || ' should have the correct tables' - ); -$$ LANGUAGE SQL; - --- tables_are( tables ) -CREATE OR REPLACE FUNCTION tables_are ( NAME[] ) -RETURNS TEXT AS $$ - SELECT _are( - 'tables', _extras('{r,p}'::char[], $1), _missing('{r,p}'::char[], $1), - 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct tables' - ); -$$ LANGUAGE SQL; - --- views_are( schema, views, description ) -CREATE OR REPLACE FUNCTION views_are ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( 'views', _extras('v', $1, $2), _missing('v', $1, $2), $3); -$$ LANGUAGE SQL; - --- views_are( views, description ) -CREATE OR REPLACE FUNCTION views_are ( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( 'views', _extras('v', $1), _missing('v', $1), $2); -$$ LANGUAGE SQL; - --- views_are( schema, views ) -CREATE OR REPLACE FUNCTION views_are ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _are( - 'views', _extras('v', $1, $2), _missing('v', $1, $2), - 'Schema ' || quote_ident($1) || ' should have the correct views' - ); -$$ LANGUAGE SQL; - --- views_are( views ) -CREATE OR REPLACE FUNCTION views_are ( NAME[] ) -RETURNS TEXT AS $$ - SELECT _are( - 'views', _extras('v', $1), _missing('v', $1), - 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views' - ); -$$ LANGUAGE SQL; - --- sequences_are( schema, sequences, description ) -CREATE OR REPLACE FUNCTION sequences_are ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( 'sequences', _extras('S', $1, $2), _missing('S', $1, $2), $3); -$$ LANGUAGE SQL; - --- sequences_are( sequences, description ) -CREATE OR REPLACE FUNCTION sequences_are ( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( 'sequences', _extras('S', $1), _missing('S', $1), $2); -$$ LANGUAGE SQL; - --- sequences_are( schema, sequences ) -CREATE OR REPLACE FUNCTION sequences_are ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _are( - 'sequences', _extras('S', $1, $2), _missing('S', $1, $2), - 'Schema ' || quote_ident($1) || ' should have the correct sequences' - ); -$$ LANGUAGE SQL; - --- sequences_are( sequences ) -CREATE OR REPLACE FUNCTION sequences_are ( NAME[] ) -RETURNS TEXT AS $$ - SELECT _are( - 'sequences', _extras('S', $1), _missing('S', $1), - 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct sequences' - ); -$$ LANGUAGE SQL; - --- functions_are( schema, functions[], description ) -CREATE OR REPLACE FUNCTION functions_are ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'functions', - ARRAY( - SELECT name FROM tap_funky WHERE schema = $1 - EXCEPT - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - ), - ARRAY( - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - EXCEPT - SELECT name FROM tap_funky WHERE schema = $1 - ), - $3 - ); -$$ LANGUAGE SQL; - --- functions_are( schema, functions[] ) -CREATE OR REPLACE FUNCTION functions_are ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT functions_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct functions' ); -$$ LANGUAGE SQL; - --- functions_are( functions[], description ) -CREATE OR REPLACE FUNCTION functions_are ( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'functions', - ARRAY( - SELECT name FROM tap_funky WHERE is_visible - AND schema NOT IN ('pg_catalog', 'information_schema') - EXCEPT - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - ), - ARRAY( - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - EXCEPT - SELECT name FROM tap_funky WHERE is_visible - AND schema NOT IN ('pg_catalog', 'information_schema') - ), - $2 - ); -$$ LANGUAGE SQL; - --- functions_are( functions[] ) -CREATE OR REPLACE FUNCTION functions_are ( NAME[] ) -RETURNS TEXT AS $$ - SELECT functions_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct functions' ); -$$ LANGUAGE SQL; - --- indexes_are( schema, table, indexes[], description ) -CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'indexes', - ARRAY( - SELECT ci.relname - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace - WHERE ct.relname = $2 - AND n.nspname = $1 - EXCEPT - SELECT $3[i] - FROM generate_series(1, array_upper($3, 1)) s(i) - ), - ARRAY( - SELECT $3[i] - FROM generate_series(1, array_upper($3, 1)) s(i) - EXCEPT - SELECT ci.relname - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace - WHERE ct.relname = $2 - AND n.nspname = $1 - ), - $4 - ); -$$ LANGUAGE SQL; - --- indexes_are( schema, table, indexes[] ) -CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT indexes_are( $1, $2, $3, 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct indexes' ); -$$ LANGUAGE SQL; - --- indexes_are( table, indexes[], description ) -CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'indexes', - ARRAY( - SELECT ci.relname - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace - WHERE ct.relname = $1 - AND pg_catalog.pg_table_is_visible(ct.oid) - AND n.nspname NOT IN ('pg_catalog', 'information_schema') - EXCEPT - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - ), - ARRAY( - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - EXCEPT - SELECT ci.relname - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace - WHERE ct.relname = $1 - AND pg_catalog.pg_table_is_visible(ct.oid) - AND n.nspname NOT IN ('pg_catalog', 'information_schema') - ), - $3 - ); -$$ LANGUAGE SQL; - --- indexes_are( table, indexes[] ) -CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT indexes_are( $1, $2, 'Table ' || quote_ident($1) || ' should have the correct indexes' ); -$$ LANGUAGE SQL; - --- users_are( users[], description ) -CREATE OR REPLACE FUNCTION users_are( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'users', - ARRAY( - SELECT usename - FROM pg_catalog.pg_user - EXCEPT - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - ), - ARRAY( - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - EXCEPT - SELECT usename - FROM pg_catalog.pg_user - ), - $2 - ); -$$ LANGUAGE SQL; - --- users_are( users[] ) -CREATE OR REPLACE FUNCTION users_are( NAME[] ) -RETURNS TEXT AS $$ - SELECT users_are( $1, 'There should be the correct users' ); -$$ LANGUAGE SQL; - --- groups_are( groups[], description ) -CREATE OR REPLACE FUNCTION groups_are( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'groups', - ARRAY( - SELECT groname - FROM pg_catalog.pg_group - EXCEPT - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - ), - ARRAY( - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - EXCEPT - SELECT groname - FROM pg_catalog.pg_group - ), - $2 - ); -$$ LANGUAGE SQL; - --- groups_are( groups[] ) -CREATE OR REPLACE FUNCTION groups_are( NAME[] ) -RETURNS TEXT AS $$ - SELECT groups_are( $1, 'There should be the correct groups' ); -$$ LANGUAGE SQL; - --- languages_are( languages[], description ) -CREATE OR REPLACE FUNCTION languages_are( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'languages', - ARRAY( - SELECT lanname - FROM pg_catalog.pg_language - WHERE lanispl - EXCEPT - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - ), - ARRAY( - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - EXCEPT - SELECT lanname - FROM pg_catalog.pg_language - WHERE lanispl - ), - $2 - ); -$$ LANGUAGE SQL; - --- languages_are( languages[] ) -CREATE OR REPLACE FUNCTION languages_are( NAME[] ) -RETURNS TEXT AS $$ - SELECT languages_are( $1, 'There should be the correct procedural languages' ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _is_trusted( NAME ) -RETURNS BOOLEAN AS $$ - SELECT lanpltrusted FROM pg_catalog.pg_language WHERE lanname = $1; -$$ LANGUAGE SQL; - --- has_language( language, description) -CREATE OR REPLACE FUNCTION has_language( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _is_trusted($1) IS NOT NULL, $2 ); -$$ LANGUAGE SQL; - --- has_language( language ) -CREATE OR REPLACE FUNCTION has_language( NAME ) -RETURNS TEXT AS $$ - SELECT ok( _is_trusted($1) IS NOT NULL, 'Procedural language ' || quote_ident($1) || ' should exist' ); -$$ LANGUAGE SQL; - --- hasnt_language( language, description) -CREATE OR REPLACE FUNCTION hasnt_language( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _is_trusted($1) IS NULL, $2 ); -$$ LANGUAGE SQL; - --- hasnt_language( language ) -CREATE OR REPLACE FUNCTION hasnt_language( NAME ) -RETURNS TEXT AS $$ - SELECT ok( _is_trusted($1) IS NULL, 'Procedural language ' || quote_ident($1) || ' should not exist' ); -$$ LANGUAGE SQL; - --- language_is_trusted( language, description ) -CREATE OR REPLACE FUNCTION language_is_trusted( NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - is_trusted boolean := _is_trusted($1); -BEGIN - IF is_trusted IS NULL THEN - RETURN fail( $2 ) || E'\n' || diag( ' Procedural language ' || quote_ident($1) || ' does not exist') ; - END IF; - RETURN ok( is_trusted, $2 ); -END; -$$ LANGUAGE plpgsql; - --- language_is_trusted( language ) -CREATE OR REPLACE FUNCTION language_is_trusted( NAME ) -RETURNS TEXT AS $$ - SELECT language_is_trusted($1, 'Procedural language ' || quote_ident($1) || ' should be trusted' ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _opc_exists( NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS ( - SELECT TRUE - FROM pg_catalog.pg_opclass oc - JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid - WHERE n.nspname = $1 - AND oc.opcname = $2 - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _opc_exists( NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS ( - SELECT TRUE - FROM pg_catalog.pg_opclass oc - WHERE oc.opcname = $1 - AND pg_opclass_is_visible(oid) - ); -$$ LANGUAGE SQL; - --- has_opclass( schema, name, description ) -CREATE OR REPLACE FUNCTION has_opclass( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _opc_exists( $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- has_opclass( schema, name ) -CREATE OR REPLACE FUNCTION has_opclass( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( _opc_exists( $1, $2 ), 'Operator class ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); -$$ LANGUAGE SQL; - --- has_opclass( name, description ) -CREATE OR REPLACE FUNCTION has_opclass( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _opc_exists( $1 ), $2) -$$ LANGUAGE SQL; - --- has_opclass( name ) -CREATE OR REPLACE FUNCTION has_opclass( NAME ) -RETURNS TEXT AS $$ - SELECT ok( _opc_exists( $1 ), 'Operator class ' || quote_ident($1) || ' should exist' ); -$$ LANGUAGE SQL; - --- hasnt_opclass( schema, name, description ) -CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _opc_exists( $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- hasnt_opclass( schema, name ) -CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( NOT _opc_exists( $1, $2 ), 'Operator class ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); -$$ LANGUAGE SQL; - --- hasnt_opclass( name, description ) -CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _opc_exists( $1 ), $2) -$$ LANGUAGE SQL; - --- hasnt_opclass( name ) -CREATE OR REPLACE FUNCTION hasnt_opclass( NAME ) -RETURNS TEXT AS $$ - SELECT ok( NOT _opc_exists( $1 ), 'Operator class ' || quote_ident($1) || ' should not exist' ); -$$ LANGUAGE SQL; - --- opclasses_are( schema, opclasses[], description ) -CREATE OR REPLACE FUNCTION opclasses_are ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'operator classes', - ARRAY( - SELECT oc.opcname - FROM pg_catalog.pg_opclass oc - JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid - WHERE n.nspname = $1 - EXCEPT - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - ), - ARRAY( - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - EXCEPT - SELECT oc.opcname - FROM pg_catalog.pg_opclass oc - JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid - WHERE n.nspname = $1 - ), - $3 - ); -$$ LANGUAGE SQL; - --- opclasses_are( schema, opclasses[] ) -CREATE OR REPLACE FUNCTION opclasses_are ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT opclasses_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct operator classes' ); -$$ LANGUAGE SQL; - --- opclasses_are( opclasses[], description ) -CREATE OR REPLACE FUNCTION opclasses_are ( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'operator classes', - ARRAY( - SELECT oc.opcname - FROM pg_catalog.pg_opclass oc - JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid - AND n.nspname NOT IN ('pg_catalog', 'information_schema') - AND pg_catalog.pg_opclass_is_visible(oc.oid) - EXCEPT - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - ), - ARRAY( - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - EXCEPT - SELECT oc.opcname - FROM pg_catalog.pg_opclass oc - JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid - AND n.nspname NOT IN ('pg_catalog', 'information_schema') - AND pg_catalog.pg_opclass_is_visible(oc.oid) - ), - $2 - ); -$$ LANGUAGE SQL; - --- opclasses_are( opclasses[] ) -CREATE OR REPLACE FUNCTION opclasses_are ( NAME[] ) -RETURNS TEXT AS $$ - SELECT opclasses_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct operator classes' ); -$$ LANGUAGE SQL; - --- rules_are( schema, table, rules[], description ) -CREATE OR REPLACE FUNCTION rules_are( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'rules', - ARRAY( - SELECT r.rulename - FROM pg_catalog.pg_rewrite r - JOIN pg_catalog.pg_class c ON c.oid = r.ev_class - JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid - WHERE c.relname = $2 - AND n.nspname = $1 - EXCEPT - SELECT $3[i] - FROM generate_series(1, array_upper($3, 1)) s(i) - ), - ARRAY( - SELECT $3[i] - FROM generate_series(1, array_upper($3, 1)) s(i) - EXCEPT - SELECT r.rulename - FROM pg_catalog.pg_rewrite r - JOIN pg_catalog.pg_class c ON c.oid = r.ev_class - JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid - WHERE c.relname = $2 - AND n.nspname = $1 - ), - $4 - ); -$$ LANGUAGE SQL; - --- rules_are( schema, table, rules[] ) -CREATE OR REPLACE FUNCTION rules_are( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT rules_are( $1, $2, $3, 'Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct rules' ); -$$ LANGUAGE SQL; - --- rules_are( table, rules[], description ) -CREATE OR REPLACE FUNCTION rules_are( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'rules', - ARRAY( - SELECT r.rulename - FROM pg_catalog.pg_rewrite r - JOIN pg_catalog.pg_class c ON c.oid = r.ev_class - JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid - WHERE c.relname = $1 - AND n.nspname NOT IN ('pg_catalog', 'information_schema') - AND pg_catalog.pg_table_is_visible(c.oid) - EXCEPT - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - ), - ARRAY( - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - EXCEPT - SELECT r.rulename - FROM pg_catalog.pg_rewrite r - JOIN pg_catalog.pg_class c ON c.oid = r.ev_class - JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid - AND c.relname = $1 - AND n.nspname NOT IN ('pg_catalog', 'information_schema') - AND pg_catalog.pg_table_is_visible(c.oid) - ), - $3 - ); -$$ LANGUAGE SQL; - --- rules_are( table, rules[] ) -CREATE OR REPLACE FUNCTION rules_are( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT rules_are( $1, $2, 'Relation ' || quote_ident($1) || ' should have the correct rules' ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _is_instead( NAME, NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT r.is_instead - FROM pg_catalog.pg_rewrite r - JOIN pg_catalog.pg_class c ON c.oid = r.ev_class - JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid - WHERE r.rulename = $3 - AND c.relname = $2 - AND n.nspname = $1 -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _is_instead( NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT r.is_instead - FROM pg_catalog.pg_rewrite r - JOIN pg_catalog.pg_class c ON c.oid = r.ev_class - WHERE r.rulename = $2 - AND c.relname = $1 - AND pg_catalog.pg_table_is_visible(c.oid) -$$ LANGUAGE SQL; - --- has_rule( schema, table, rule, description ) -CREATE OR REPLACE FUNCTION has_rule( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _is_instead($1, $2, $3) IS NOT NULL, $4 ); -$$ LANGUAGE SQL; - --- has_rule( schema, table, rule ) -CREATE OR REPLACE FUNCTION has_rule( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( _is_instead($1, $2, $3) IS NOT NULL, 'Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should have rule ' || quote_ident($3) ); -$$ LANGUAGE SQL; - --- has_rule( table, rule, description ) -CREATE OR REPLACE FUNCTION has_rule( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _is_instead($1, $2) IS NOT NULL, $3 ); -$$ LANGUAGE SQL; - --- has_rule( table, rule ) -CREATE OR REPLACE FUNCTION has_rule( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( _is_instead($1, $2) IS NOT NULL, 'Relation ' || quote_ident($1) || ' should have rule ' || quote_ident($2) ); -$$ LANGUAGE SQL; - --- hasnt_rule( schema, table, rule, description ) -CREATE OR REPLACE FUNCTION hasnt_rule( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _is_instead($1, $2, $3) IS NULL, $4 ); -$$ LANGUAGE SQL; - --- hasnt_rule( schema, table, rule ) -CREATE OR REPLACE FUNCTION hasnt_rule( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( _is_instead($1, $2, $3) IS NULL, 'Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should not have rule ' || quote_ident($3) ); -$$ LANGUAGE SQL; - --- hasnt_rule( table, rule, description ) -CREATE OR REPLACE FUNCTION hasnt_rule( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _is_instead($1, $2) IS NULL, $3 ); -$$ LANGUAGE SQL; - --- hasnt_rule( table, rule ) -CREATE OR REPLACE FUNCTION hasnt_rule( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( _is_instead($1, $2) IS NULL, 'Relation ' || quote_ident($1) || ' should not have rule ' || quote_ident($2) ); -$$ LANGUAGE SQL; - --- rule_is_instead( schema, table, rule, description ) -CREATE OR REPLACE FUNCTION rule_is_instead( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - is_it boolean := _is_instead($1, $2, $3); -BEGIN - IF is_it IS NOT NULL THEN RETURN ok( is_it, $4 ); END IF; - RETURN ok( FALSE, $4 ) || E'\n' || diag( - ' Rule ' || quote_ident($3) || ' does not exist' - ); -END; -$$ LANGUAGE plpgsql; - --- rule_is_instead( schema, table, rule ) -CREATE OR REPLACE FUNCTION rule_is_instead( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT rule_is_instead( $1, $2, $3, 'Rule ' || quote_ident($3) || ' on relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should be an INSTEAD rule' ); -$$ LANGUAGE SQL; - --- rule_is_instead( table, rule, description ) -CREATE OR REPLACE FUNCTION rule_is_instead( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - is_it boolean := _is_instead($1, $2); -BEGIN - IF is_it IS NOT NULL THEN RETURN ok( is_it, $3 ); END IF; - RETURN ok( FALSE, $3 ) || E'\n' || diag( - ' Rule ' || quote_ident($2) || ' does not exist' - ); -END; -$$ LANGUAGE plpgsql; - --- rule_is_instead( table, rule ) -CREATE OR REPLACE FUNCTION rule_is_instead( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT rule_is_instead($1, $2, 'Rule ' || quote_ident($2) || ' on relation ' || quote_ident($1) || ' should be an INSTEAD rule' ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _expand_on( char ) -RETURNS text AS $$ - SELECT CASE $1 - WHEN '1' THEN 'SELECT' - WHEN '2' THEN 'UPDATE' - WHEN '3' THEN 'INSERT' - WHEN '4' THEN 'DELETE' - ELSE 'UNKNOWN' END -$$ LANGUAGE SQL IMMUTABLE; - -CREATE OR REPLACE FUNCTION _contract_on( TEXT ) -RETURNS "char" AS $$ - SELECT CASE substring(LOWER($1) FROM 1 FOR 1) - WHEN 's' THEN '1'::"char" - WHEN 'u' THEN '2'::"char" - WHEN 'i' THEN '3'::"char" - WHEN 'd' THEN '4'::"char" - ELSE '0'::"char" END -$$ LANGUAGE SQL IMMUTABLE; - -CREATE OR REPLACE FUNCTION _rule_on( NAME, NAME, NAME ) -RETURNS "char" AS $$ - SELECT r.ev_type - FROM pg_catalog.pg_rewrite r - JOIN pg_catalog.pg_class c ON c.oid = r.ev_class - JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid - WHERE r.rulename = $3 - AND c.relname = $2 - AND n.nspname = $1 -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _rule_on( NAME, NAME ) -RETURNS "char" AS $$ - SELECT r.ev_type - FROM pg_catalog.pg_rewrite r - JOIN pg_catalog.pg_class c ON c.oid = r.ev_class - WHERE r.rulename = $2 - AND c.relname = $1 -$$ LANGUAGE SQL; - --- rule_is_on( schema, table, rule, event, description ) -CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - want char := _contract_on($4); - have char := _rule_on($1, $2, $3); -BEGIN - IF have IS NOT NULL THEN - RETURN is( _expand_on(have), _expand_on(want), $5 ); - END IF; - - RETURN ok( false, $5 ) || E'\n' || diag( - ' Rule ' || quote_ident($3) || ' does not exist on ' - || quote_ident($1) || '.' || quote_ident($2) - ); -END; -$$ LANGUAGE plpgsql; - --- rule_is_on( schema, table, rule, event ) -CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT rule_is_on( - $1, $2, $3, $4, - 'Rule ' || quote_ident($3) || ' should be on ' || _expand_on(_contract_on($4)::char) - || ' to ' || quote_ident($1) || '.' || quote_ident($2) - ); -$$ LANGUAGE SQL; - --- rule_is_on( table, rule, event, description ) -CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - want char := _contract_on($3); - have char := _rule_on($1, $2); -BEGIN - IF have IS NOT NULL THEN - RETURN is( _expand_on(have), _expand_on(want), $4 ); - END IF; - - RETURN ok( false, $4 ) || E'\n' || diag( - ' Rule ' || quote_ident($2) || ' does not exist on ' - || quote_ident($1) - ); -END; -$$ LANGUAGE plpgsql; - --- rule_is_on( table, rule, event ) -CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT rule_is_on( - $1, $2, $3, - 'Rule ' || quote_ident($2) || ' should be on ' - || _expand_on(_contract_on($3)::char) || ' to ' || quote_ident($1) - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _nosuch( NAME, NAME, NAME[]) -RETURNS TEXT AS $$ - SELECT E'\n' || diag( - ' Function ' - || CASE WHEN $1 IS NOT NULL THEN quote_ident($1) || '.' ELSE '' END - || quote_ident($2) || '(' - || array_to_string($3, ', ') || ') does not exist' - ); -$$ LANGUAGE SQL IMMUTABLE; - -CREATE OR REPLACE FUNCTION _func_compare( NAME, NAME, NAME[], anyelement, anyelement, TEXT) -RETURNS TEXT AS $$ - SELECT CASE WHEN $4 IS NULL - THEN ok( FALSE, $6 ) || _nosuch($1, $2, $3) - ELSE is( $4, $5, $6 ) - END; -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _func_compare( NAME, NAME, NAME[], boolean, TEXT) -RETURNS TEXT AS $$ - SELECT CASE WHEN $4 IS NULL - THEN ok( FALSE, $5 ) || _nosuch($1, $2, $3) - ELSE ok( $4, $5 ) - END; -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _func_compare( NAME, NAME, anyelement, anyelement, TEXT) -RETURNS TEXT AS $$ - SELECT CASE WHEN $3 IS NULL - THEN ok( FALSE, $5 ) || _nosuch($1, $2, '{}') - ELSE is( $3, $4, $5 ) - END; -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _func_compare( NAME, NAME, boolean, TEXT) -RETURNS TEXT AS $$ - SELECT CASE WHEN $3 IS NULL - THEN ok( FALSE, $4 ) || _nosuch($1, $2, '{}') - ELSE ok( $3, $4 ) - END; -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _lang ( NAME, NAME, NAME[] ) -RETURNS NAME AS $$ - SELECT l.lanname - FROM tap_funky f - JOIN pg_catalog.pg_language l ON f.langoid = l.oid - WHERE f.schema = $1 - and f.name = $2 - AND f.args = _funkargs($3) -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _lang ( NAME, NAME ) -RETURNS NAME AS $$ - SELECT l.lanname - FROM tap_funky f - JOIN pg_catalog.pg_language l ON f.langoid = l.oid - WHERE f.schema = $1 - and f.name = $2 -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _lang ( NAME, NAME[] ) -RETURNS NAME AS $$ - SELECT l.lanname - FROM tap_funky f - JOIN pg_catalog.pg_language l ON f.langoid = l.oid - WHERE f.name = $1 - AND f.args = _funkargs($2) - AND f.is_visible; -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _lang ( NAME ) -RETURNS NAME AS $$ - SELECT l.lanname - FROM tap_funky f - JOIN pg_catalog.pg_language l ON f.langoid = l.oid - WHERE f.name = $1 - AND f.is_visible; -$$ LANGUAGE SQL; - --- function_lang_is( schema, function, args[], language, description ) -CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, NAME[], NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, $3, _lang($1, $2, $3), $4, $5 ); -$$ LANGUAGE SQL; - --- function_lang_is( schema, function, args[], language ) -CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, NAME[], NAME ) -RETURNS TEXT AS $$ - SELECT function_lang_is( - $1, $2, $3, $4, - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should be written in ' || quote_ident($4) - ); -$$ LANGUAGE SQL; - --- function_lang_is( schema, function, language, description ) -CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, _lang($1, $2), $3, $4 ); -$$ LANGUAGE SQL; - --- function_lang_is( schema, function, language ) -CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT function_lang_is( - $1, $2, $3, - 'Function ' || quote_ident($1) || '.' || quote_ident($2) - || '() should be written in ' || quote_ident($3) - ); -$$ LANGUAGE SQL; - --- function_lang_is( function, args[], language, description ) -CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME[], NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, $2, _lang($1, $2), $3, $4 ); -$$ LANGUAGE SQL; - --- function_lang_is( function, args[], language ) -CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME[], NAME ) -RETURNS TEXT AS $$ - SELECT function_lang_is( - $1, $2, $3, - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should be written in ' || quote_ident($3) - ); -$$ LANGUAGE SQL; - --- function_lang_is( function, language, description ) -CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, _lang($1), $2, $3 ); -$$ LANGUAGE SQL; - --- function_lang_is( function, language ) -CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT function_lang_is( - $1, $2, - 'Function ' || quote_ident($1) - || '() should be written in ' || quote_ident($2) - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _returns ( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT returns - FROM tap_funky - WHERE schema = $1 - AND name = $2 - AND args = _funkargs($3) -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _returns ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT returns FROM tap_funky WHERE schema = $1 AND name = $2 -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _returns ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT returns - FROM tap_funky - WHERE name = $1 - AND args = _funkargs($2) - AND is_visible; -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _returns ( NAME ) -RETURNS TEXT AS $$ - SELECT returns FROM tap_funky WHERE name = $1 AND is_visible; -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _retval(TEXT) -RETURNS TEXT AS $$ -DECLARE - setof TEXT := substring($1 FROM '^setof[[:space:]]+'); -BEGIN - IF setof IS NULL THEN RETURN _typename($1); END IF; - RETURN setof || _typename(substring($1 FROM char_length(setof)+1)); -END; -$$ LANGUAGE plpgsql; - --- function_returns( schema, function, args[], type, description ) -CREATE OR REPLACE FUNCTION function_returns( NAME, NAME, NAME[], TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, $3, _returns($1, $2, $3), _retval($4), $5 ); -$$ LANGUAGE SQL; - --- function_returns( schema, function, args[], type ) -CREATE OR REPLACE FUNCTION function_returns( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT function_returns( - $1, $2, $3, $4, - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should return ' || $4 - ); -$$ LANGUAGE SQL; - --- function_returns( schema, function, type, description ) -CREATE OR REPLACE FUNCTION function_returns( NAME, NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, _returns($1, $2), _retval($3), $4 ); -$$ LANGUAGE SQL; - --- function_returns( schema, function, type ) -CREATE OR REPLACE FUNCTION function_returns( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT function_returns( - $1, $2, $3, - 'Function ' || quote_ident($1) || '.' || quote_ident($2) - || '() should return ' || $3 - ); -$$ LANGUAGE SQL; - --- function_returns( function, args[], type, description ) -CREATE OR REPLACE FUNCTION function_returns( NAME, NAME[], TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, $2, _returns($1, $2), _retval($3), $4 ); -$$ LANGUAGE SQL; - --- function_returns( function, args[], type ) -CREATE OR REPLACE FUNCTION function_returns( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT function_returns( - $1, $2, $3, - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should return ' || $3 - ); -$$ LANGUAGE SQL; - --- function_returns( function, type, description ) -CREATE OR REPLACE FUNCTION function_returns( NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, _returns($1), _retval($2), $3 ); -$$ LANGUAGE SQL; - --- function_returns( function, type ) -CREATE OR REPLACE FUNCTION function_returns( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT function_returns( - $1, $2, - 'Function ' || quote_ident($1) || '() should return ' || $2 - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _definer ( NAME, NAME, NAME[] ) -RETURNS BOOLEAN AS $$ - SELECT is_definer - FROM tap_funky - WHERE schema = $1 - AND name = $2 - AND args = _funkargs($3) -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _definer ( NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT is_definer FROM tap_funky WHERE schema = $1 AND name = $2 -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _definer ( NAME, NAME[] ) -RETURNS BOOLEAN AS $$ - SELECT is_definer - FROM tap_funky - WHERE name = $1 - AND args = _funkargs($2) - AND is_visible; -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _definer ( NAME ) -RETURNS BOOLEAN AS $$ - SELECT is_definer FROM tap_funky WHERE name = $1 AND is_visible; -$$ LANGUAGE SQL; - --- is_definer( schema, function, args[], description ) -CREATE OR REPLACE FUNCTION is_definer ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, $3, _definer($1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- is_definer( schema, function, args[] ) -CREATE OR REPLACE FUNCTION is_definer( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - _definer($1, $2, $3), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should be security definer' - ); -$$ LANGUAGE sql; - --- is_definer( schema, function, description ) -CREATE OR REPLACE FUNCTION is_definer ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, _definer($1, $2), $3 ); -$$ LANGUAGE SQL; - --- is_definer( schema, function ) -CREATE OR REPLACE FUNCTION is_definer( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _definer($1, $2), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should be security definer' - ); -$$ LANGUAGE sql; - --- is_definer( function, args[], description ) -CREATE OR REPLACE FUNCTION is_definer ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, $2, _definer($1, $2), $3 ); -$$ LANGUAGE SQL; - --- is_definer( function, args[] ) -CREATE OR REPLACE FUNCTION is_definer( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - _definer($1, $2), - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should be security definer' - ); -$$ LANGUAGE sql; - --- is_definer( function, description ) -CREATE OR REPLACE FUNCTION is_definer( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, _definer($1), $2 ); -$$ LANGUAGE sql; - --- is_definer( function ) -CREATE OR REPLACE FUNCTION is_definer( NAME ) -RETURNS TEXT AS $$ - SELECT ok( _definer($1), 'Function ' || quote_ident($1) || '() should be security definer' ); -$$ LANGUAGE sql; - --- isnt_definer( schema, function, args[], description ) -CREATE OR REPLACE FUNCTION isnt_definer ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, $3, NOT _definer($1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- isnt_definer( schema, function, args[] ) -CREATE OR REPLACE FUNCTION isnt_definer( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _definer($1, $2, $3), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should not be security definer' - ); -$$ LANGUAGE sql; - --- isnt_definer( schema, function, description ) -CREATE OR REPLACE FUNCTION isnt_definer ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, NOT _definer($1, $2), $3 ); -$$ LANGUAGE SQL; - --- isnt_definer( schema, function ) -CREATE OR REPLACE FUNCTION isnt_definer( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _definer($1, $2), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not be security definer' - ); -$$ LANGUAGE sql; - --- isnt_definer( function, args[], description ) -CREATE OR REPLACE FUNCTION isnt_definer ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, $2, NOT _definer($1, $2), $3 ); -$$ LANGUAGE SQL; - --- isnt_definer( function, args[] ) -CREATE OR REPLACE FUNCTION isnt_definer( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _definer($1, $2), - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should not be security definer' - ); -$$ LANGUAGE sql; - --- isnt_definer( function, description ) -CREATE OR REPLACE FUNCTION isnt_definer( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, NOT _definer($1), $2 ); -$$ LANGUAGE sql; - --- isnt_definer( function ) -CREATE OR REPLACE FUNCTION isnt_definer( NAME ) -RETURNS TEXT AS $$ - SELECT ok( NOT _definer($1), 'Function ' || quote_ident($1) || '() should not be security definer' ); -$$ LANGUAGE sql; - --- Returns true if the specified function exists and is the specified type, --- false if it exists and is not the specified type, and NULL if it does not --- exist. Types are f for a normal function, p for a procedure, a for an --- aggregate function, or w for a window function --- _type_func(type, schema, function, args[]) -CREATE OR REPLACE FUNCTION _type_func ( "char", NAME, NAME, NAME[] ) -RETURNS BOOLEAN AS $$ - SELECT kind = $1 - FROM tap_funky - WHERE schema = $2 - AND name = $3 - AND args = _funkargs($4) -$$ LANGUAGE SQL; - --- _type_func(type, schema, function) -CREATE OR REPLACE FUNCTION _type_func ( "char", NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT kind = $1 FROM tap_funky WHERE schema = $2 AND name = $3 -$$ LANGUAGE SQL; - --- _type_func(type, function, args[]) -CREATE OR REPLACE FUNCTION _type_func ( "char", NAME, NAME[] ) -RETURNS BOOLEAN AS $$ - SELECT kind = $1 - FROM tap_funky - WHERE name = $2 - AND args = _funkargs($3) - AND is_visible; -$$ LANGUAGE SQL; - --- _type_func(type, function) -CREATE OR REPLACE FUNCTION _type_func ( "char", NAME ) -RETURNS BOOLEAN AS $$ - SELECT kind = $1 FROM tap_funky WHERE name = $2 AND is_visible; -$$ LANGUAGE SQL; - --- is_aggregate( schema, function, args[], description ) -CREATE OR REPLACE FUNCTION is_aggregate ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, $3, _type_func( 'a', $1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- is_aggregate( schema, function, args[] ) -CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _func_compare( - $1, $2, $3, _type_func('a', $1, $2, $3), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should be an aggregate function' - ); -$$ LANGUAGE sql; - --- is_aggregate( schema, function, description ) -CREATE OR REPLACE FUNCTION is_aggregate ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, _type_func('a', $1, $2), $3 ); -$$ LANGUAGE SQL; - --- is_aggregate( schema, function ) -CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _func_compare( - $1, $2, _type_func('a', $1, $2), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should be an aggregate function' - ); -$$ LANGUAGE sql; - --- is_aggregate( function, args[], description ) -CREATE OR REPLACE FUNCTION is_aggregate ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare( NULL, $1, $2, _type_func('a', $1, $2), $3 ); -$$ LANGUAGE SQL; - --- is_aggregate( function, args[] ) -CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _func_compare( - NULL, $1, $2, _type_func('a', $1, $2), - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should be an aggregate function' - ); -$$ LANGUAGE sql; - --- is_aggregate( function, description ) -CREATE OR REPLACE FUNCTION is_aggregate( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, _type_func('a', $1), $2 ); -$$ LANGUAGE sql; - --- is_aggregate( function ) -CREATE OR REPLACE FUNCTION is_aggregate( NAME ) -RETURNS TEXT AS $$ - SELECT _func_compare( - NULL, $1, _type_func('a', $1), - 'Function ' || quote_ident($1) || '() should be an aggregate function' - ); -$$ LANGUAGE sql; - --- isnt_aggregate( schema, function, args[], description ) -CREATE OR REPLACE FUNCTION isnt_aggregate ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, $3, NOT _type_func('a', $1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- isnt_aggregate( schema, function, args[] ) -CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _func_compare( - $1, $2, $3, NOT _type_func('a', $1, $2, $3), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should not be an aggregate function' - ); -$$ LANGUAGE sql; - --- isnt_aggregate( schema, function, description ) -CREATE OR REPLACE FUNCTION isnt_aggregate ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, NOT _type_func('a', $1, $2), $3 ); -$$ LANGUAGE SQL; - --- isnt_aggregate( schema, function ) -CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _func_compare( - $1, $2, NOT _type_func('a', $1, $2), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not be an aggregate function' - ); -$$ LANGUAGE sql; - --- isnt_aggregate( function, args[], description ) -CREATE OR REPLACE FUNCTION isnt_aggregate ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, $2, NOT _type_func('a', $1, $2), $3 ); -$$ LANGUAGE SQL; - --- isnt_aggregate( function, args[] ) -CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _func_compare( - NULL, $1, $2, NOT _type_func('a', $1, $2), - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should not be an aggregate function' - ); -$$ LANGUAGE sql; - --- isnt_aggregate( function, description ) -CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, NOT _type_func('a', $1), $2 ); -$$ LANGUAGE sql; - --- isnt_aggregate( function ) -CREATE OR REPLACE FUNCTION isnt_aggregate( NAME ) -RETURNS TEXT AS $$ - SELECT _func_compare( - NULL, $1, NOT _type_func('a', $1), - 'Function ' || quote_ident($1) || '() should not be an aggregate function' - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _strict ( NAME, NAME, NAME[] ) -RETURNS BOOLEAN AS $$ - SELECT is_strict - FROM tap_funky - WHERE schema = $1 - AND name = $2 - AND args = _funkargs($3) -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _strict ( NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT is_strict FROM tap_funky WHERE schema = $1 AND name = $2 -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _strict ( NAME, NAME[] ) -RETURNS BOOLEAN AS $$ - SELECT is_strict - FROM tap_funky - WHERE name = $1 - AND args = _funkargs($2) - AND is_visible; -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _strict ( NAME ) -RETURNS BOOLEAN AS $$ - SELECT is_strict FROM tap_funky WHERE name = $1 AND is_visible; -$$ LANGUAGE SQL; - --- is_strict( schema, function, args[], description ) -CREATE OR REPLACE FUNCTION is_strict ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, $3, _strict($1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- is_strict( schema, function, args[] ) -CREATE OR REPLACE FUNCTION is_strict( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - _strict($1, $2, $3), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should be strict' - ); -$$ LANGUAGE sql; - --- is_strict( schema, function, description ) -CREATE OR REPLACE FUNCTION is_strict ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, _strict($1, $2), $3 ); -$$ LANGUAGE SQL; - --- is_strict( schema, function ) -CREATE OR REPLACE FUNCTION is_strict( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _strict($1, $2), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should be strict' - ); -$$ LANGUAGE sql; - --- is_strict( function, args[], description ) -CREATE OR REPLACE FUNCTION is_strict ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, $2, _strict($1, $2), $3 ); -$$ LANGUAGE SQL; - --- is_strict( function, args[] ) -CREATE OR REPLACE FUNCTION is_strict( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - _strict($1, $2), - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should be strict' - ); -$$ LANGUAGE sql; - --- is_strict( function, description ) -CREATE OR REPLACE FUNCTION is_strict( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, _strict($1), $2 ); -$$ LANGUAGE sql; - --- is_strict( function ) -CREATE OR REPLACE FUNCTION is_strict( NAME ) -RETURNS TEXT AS $$ - SELECT ok( _strict($1), 'Function ' || quote_ident($1) || '() should be strict' ); -$$ LANGUAGE sql; - --- isnt_strict( schema, function, args[], description ) -CREATE OR REPLACE FUNCTION isnt_strict ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, $3, NOT _strict($1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- isnt_strict( schema, function, args[] ) -CREATE OR REPLACE FUNCTION isnt_strict( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _strict($1, $2, $3), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should not be strict' - ); -$$ LANGUAGE sql; - --- isnt_strict( schema, function, description ) -CREATE OR REPLACE FUNCTION isnt_strict ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, NOT _strict($1, $2), $3 ); -$$ LANGUAGE SQL; - --- isnt_strict( schema, function ) -CREATE OR REPLACE FUNCTION isnt_strict( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _strict($1, $2), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not be strict' - ); -$$ LANGUAGE sql; - --- isnt_strict( function, args[], description ) -CREATE OR REPLACE FUNCTION isnt_strict ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, $2, NOT _strict($1, $2), $3 ); -$$ LANGUAGE SQL; - --- isnt_strict( function, args[] ) -CREATE OR REPLACE FUNCTION isnt_strict( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _strict($1, $2), - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should not be strict' - ); -$$ LANGUAGE sql; - --- isnt_strict( function, description ) -CREATE OR REPLACE FUNCTION isnt_strict( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, NOT _strict($1), $2 ); -$$ LANGUAGE sql; - --- isnt_strict( function ) -CREATE OR REPLACE FUNCTION isnt_strict( NAME ) -RETURNS TEXT AS $$ - SELECT ok( NOT _strict($1), 'Function ' || quote_ident($1) || '() should not be strict' ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _expand_vol( char ) -RETURNS TEXT AS $$ - SELECT CASE $1 - WHEN 'i' THEN 'IMMUTABLE' - WHEN 's' THEN 'STABLE' - WHEN 'v' THEN 'VOLATILE' - ELSE 'UNKNOWN' END -$$ LANGUAGE SQL IMMUTABLE; - -CREATE OR REPLACE FUNCTION _refine_vol( text ) -RETURNS text AS $$ - SELECT _expand_vol(substring(LOWER($1) FROM 1 FOR 1)::char); -$$ LANGUAGE SQL IMMUTABLE; - -CREATE OR REPLACE FUNCTION _vol ( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _expand_vol(volatility) - FROM tap_funky f - WHERE f.schema = $1 - and f.name = $2 - AND f.args = _funkargs($3) -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _vol ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _expand_vol(volatility) FROM tap_funky f - WHERE f.schema = $1 and f.name = $2 -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _vol ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _expand_vol(volatility) - FROM tap_funky f - WHERE f.name = $1 - AND f.args = _funkargs($2) - AND f.is_visible; -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _vol ( NAME ) -RETURNS TEXT AS $$ - SELECT _expand_vol(volatility) FROM tap_funky f - WHERE f.name = $1 AND f.is_visible; -$$ LANGUAGE SQL; - --- volatility_is( schema, function, args[], volatility, description ) -CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME, NAME[], TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, $3, _vol($1, $2, $3), _refine_vol($4), $5 ); -$$ LANGUAGE SQL; - --- volatility_is( schema, function, args[], volatility ) -CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT volatility_is( - $1, $2, $3, $4, - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should be ' || _refine_vol($4) - ); -$$ LANGUAGE SQL; - --- volatility_is( schema, function, volatility, description ) -CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, _vol($1, $2), _refine_vol($3), $4 ); -$$ LANGUAGE SQL; - --- volatility_is( schema, function, volatility ) -CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT volatility_is( - $1, $2, $3, - 'Function ' || quote_ident($1) || '.' || quote_ident($2) - || '() should be ' || _refine_vol($3) - ); -$$ LANGUAGE SQL; - --- volatility_is( function, args[], volatility, description ) -CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME[], TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, $2, _vol($1, $2), _refine_vol($3), $4 ); -$$ LANGUAGE SQL; - --- volatility_is( function, args[], volatility ) -CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT volatility_is( - $1, $2, $3, - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should be ' || _refine_vol($3) - ); -$$ LANGUAGE SQL; - --- volatility_is( function, volatility, description ) -CREATE OR REPLACE FUNCTION volatility_is( NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, _vol($1), _refine_vol($2), $3 ); -$$ LANGUAGE SQL; - --- volatility_is( function, volatility ) -CREATE OR REPLACE FUNCTION volatility_is( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT volatility_is( - $1, $2, - 'Function ' || quote_ident($1) || '() should be ' || _refine_vol($2) - ); -$$ LANGUAGE SQL; - --- check_test( test_output, pass, name, description, diag, match_diag ) -CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT, BOOLEAN ) -RETURNS SETOF TEXT AS $$ -DECLARE - tnumb INTEGER; - aok BOOLEAN; - adescr TEXT; - res BOOLEAN; - descr TEXT; - adiag TEXT; - have ALIAS FOR $1; - eok ALIAS FOR $2; - name ALIAS FOR $3; - edescr ALIAS FOR $4; - ediag ALIAS FOR $5; - matchit ALIAS FOR $6; -BEGIN - -- What test was it that just ran? - tnumb := currval('__tresults___numb_seq'); - - -- Fetch the results. - aok := substring(have, 1, 2) = 'ok'; - adescr := COALESCE(substring(have FROM E'(?:not )?ok [[:digit:]]+ - ([^\n]+)'), ''); - - -- Now delete those results. - EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb; - IF NOT aok THEN PERFORM _set('failed', _get('failed') - 1); END IF; - - -- Set up the description. - descr := coalesce( name || ' ', 'Test ' ) || 'should '; - - -- So, did the test pass? - RETURN NEXT is( - aok, - eok, - descr || CASE eok WHEN true then 'pass' ELSE 'fail' END - ); - - -- Was the description as expected? - IF edescr IS NOT NULL THEN - RETURN NEXT is( - adescr, - edescr, - descr || 'have the proper description' - ); - END IF; - - -- Were the diagnostics as expected? - IF ediag IS NOT NULL THEN - -- Remove ok and the test number. - adiag := substring( - have - FROM CASE WHEN aok THEN 4 ELSE 9 END + char_length(tnumb::text) - ); - - -- Remove the description, if there is one. - IF adescr <> '' THEN - adiag := substring( - adiag FROM 1 + char_length( ' - ' || substr(diag( adescr ), 3) ) - ); - END IF; - - IF NOT aok THEN - -- Remove failure message from ok(). - adiag := substring(adiag FROM 1 + char_length(diag( - 'Failed test ' || tnumb || - CASE adescr WHEN '' THEN '' ELSE COALESCE(': "' || adescr || '"', '') END - ))); - END IF; - - IF ediag <> '' THEN - -- Remove the space before the diagnostics. - adiag := substring(adiag FROM 2); - END IF; - - -- Remove the #s. - adiag := replace( substring(adiag from 3), E'\n# ', E'\n' ); - - -- Now compare the diagnostics. - IF matchit THEN - RETURN NEXT matches( - adiag, - ediag, - descr || 'have the proper diagnostics' - ); - ELSE - RETURN NEXT is( - adiag, - ediag, - descr || 'have the proper diagnostics' - ); - END IF; - END IF; - - -- And we're done - RETURN; -END; -$$ LANGUAGE plpgsql; - --- check_test( test_output, pass, name, description, diag ) -CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) -RETURNS SETOF TEXT AS $$ - SELECT * FROM check_test( $1, $2, $3, $4, $5, FALSE ); -$$ LANGUAGE sql; - --- check_test( test_output, pass, name, description ) -CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ) -RETURNS SETOF TEXT AS $$ - SELECT * FROM check_test( $1, $2, $3, $4, NULL, FALSE ); -$$ LANGUAGE sql; - --- check_test( test_output, pass, name ) -CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT ) -RETURNS SETOF TEXT AS $$ - SELECT * FROM check_test( $1, $2, $3, NULL, NULL, FALSE ); -$$ LANGUAGE sql; - --- check_test( test_output, pass ) -CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN ) -RETURNS SETOF TEXT AS $$ - SELECT * FROM check_test( $1, $2, NULL, NULL, NULL, FALSE ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT, TEXT ) -RETURNS TEXT[] AS $$ - SELECT ARRAY( - SELECT DISTINCT (quote_ident(n.nspname) || '.' || quote_ident(p.proname)) COLLATE "C" AS pname - FROM pg_catalog.pg_proc p - JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid - WHERE n.nspname = $1 - AND p.proname ~ $2 - AND ($3 IS NULL OR p.proname !~ $3) - ORDER BY pname - ); -$$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT ) -RETURNS TEXT[] AS $$ - SELECT findfuncs( $1, $2, NULL ) -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION findfuncs( TEXT, TEXT ) -RETURNS TEXT[] AS $$ - SELECT ARRAY( - SELECT DISTINCT (quote_ident(n.nspname) || '.' || quote_ident(p.proname)) COLLATE "C" AS pname - FROM pg_catalog.pg_proc p - JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid - WHERE pg_catalog.pg_function_is_visible(p.oid) - AND p.proname ~ $1 - AND ($2 IS NULL OR p.proname !~ $2) - ORDER BY pname - ); -$$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION findfuncs( TEXT ) -RETURNS TEXT[] AS $$ - SELECT findfuncs( $1, NULL ) -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _runem( text[], boolean ) -RETURNS SETOF TEXT AS $$ -DECLARE - tap text; - lbound int := array_lower($1, 1); -BEGIN - IF lbound IS NULL THEN RETURN; END IF; - FOR i IN lbound..array_upper($1, 1) LOOP - -- Send the name of the function to diag if warranted. - IF $2 THEN RETURN NEXT diag( $1[i] || '()' ); END IF; - -- Execute the tap function and return its results. - FOR tap IN EXECUTE 'SELECT * FROM ' || $1[i] || '()' LOOP - RETURN NEXT tap; - END LOOP; - END LOOP; - RETURN; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION _is_verbose() -RETURNS BOOLEAN AS $$ - SELECT current_setting('client_min_messages') NOT IN ( - 'warning', 'error', 'fatal', 'panic' - ); -$$ LANGUAGE sql STABLE; - --- do_tap( schema, pattern ) -CREATE OR REPLACE FUNCTION do_tap( name, text ) -RETURNS SETOF TEXT AS $$ - SELECT * FROM _runem( findfuncs($1, $2), _is_verbose() ); -$$ LANGUAGE sql; - --- do_tap( schema ) -CREATE OR REPLACE FUNCTION do_tap( name ) -RETURNS SETOF TEXT AS $$ - SELECT * FROM _runem( findfuncs($1, '^test'), _is_verbose() ); -$$ LANGUAGE sql; - --- do_tap( pattern ) -CREATE OR REPLACE FUNCTION do_tap( text ) -RETURNS SETOF TEXT AS $$ - SELECT * FROM _runem( findfuncs($1), _is_verbose() ); -$$ LANGUAGE sql; - --- do_tap() -CREATE OR REPLACE FUNCTION do_tap( ) -RETURNS SETOF TEXT AS $$ - SELECT * FROM _runem( findfuncs('^test'), _is_verbose()); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _currtest() -RETURNS INTEGER AS $$ -BEGIN - RETURN currval('__tresults___numb_seq'); -EXCEPTION - WHEN object_not_in_prerequisite_state THEN RETURN 0; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION _cleanup() -RETURNS boolean AS $$ - DROP SEQUENCE __tresults___numb_seq; - DROP TABLE __tcache__; - DROP SEQUENCE __tcache___id_seq; - SELECT TRUE; -$$ LANGUAGE sql; - --- diag_test_name ( test_name ) -CREATE OR REPLACE FUNCTION diag_test_name(TEXT) -RETURNS TEXT AS $$ - SELECT diag($1 || '()'); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _runner( text[], text[], text[], text[], text[] ) -RETURNS SETOF TEXT AS $$ -DECLARE - startup ALIAS FOR $1; - shutdown ALIAS FOR $2; - setup ALIAS FOR $3; - teardown ALIAS FOR $4; - tests ALIAS FOR $5; - tap TEXT; - tfaild INTEGER := 0; - ffaild INTEGER := 0; - tnumb INTEGER := 0; - fnumb INTEGER := 0; - tok BOOLEAN := TRUE; -BEGIN - BEGIN - -- No plan support. - PERFORM * FROM no_plan(); - FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; - EXCEPTION - -- Catch all exceptions and simply rethrow custom exceptions. This - -- will roll back everything in the above block. - WHEN raise_exception THEN RAISE EXCEPTION '%', SQLERRM; - END; - - -- Record how startup tests have failed. - tfaild := num_failed(); - - FOR i IN 1..COALESCE(array_upper(tests, 1), 0) LOOP - - -- What subtest are we running? - RETURN NEXT diag_test_name('Subtest: ' || tests[i]); - - -- Reset the results. - tok := TRUE; - tnumb := COALESCE(_get('curr_test'), 0); - - IF tnumb > 0 THEN - EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; - PERFORM _set('curr_test', 0); - PERFORM _set('failed', 0); - END IF; - - DECLARE - errstate text; - errmsg text; - detail text; - hint text; - context text; - schname text; - tabname text; - colname text; - chkname text; - typname text; - BEGIN - BEGIN - -- Run the setup functions. - FOR tap IN SELECT * FROM _runem(setup, false) LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Run the actual test function. - FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Run the teardown functions. - FOR tap IN SELECT * FROM _runem(teardown, false) LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Emit the plan. - fnumb := COALESCE(_get('curr_test'), 0); - RETURN NEXT ' 1..' || fnumb; - - -- Emit any error messages. - IF fnumb = 0 THEN - RETURN NEXT ' # No tests run!'; - tok = false; - ELSE - -- Report failures. - ffaild := num_failed(); - IF ffaild > 0 THEN - tok := FALSE; - RETURN NEXT ' ' || diag( - 'Looks like you failed ' || ffaild || ' test' || - CASE ffaild WHEN 1 THEN '' ELSE 's' END - || ' of ' || fnumb - ); - END IF; - END IF; - - EXCEPTION WHEN OTHERS THEN - -- Something went wrong. Record that fact. - errstate := SQLSTATE; - errmsg := SQLERRM; - GET STACKED DIAGNOSTICS - detail = PG_EXCEPTION_DETAIL, - hint = PG_EXCEPTION_HINT, - context = PG_EXCEPTION_CONTEXT, - schname = SCHEMA_NAME, - tabname = TABLE_NAME, - colname = COLUMN_NAME, - chkname = CONSTRAINT_NAME, - typname = PG_DATATYPE_NAME; - END; - - -- Always raise an exception to rollback any changes. - RAISE EXCEPTION '__TAP_ROLLBACK__'; - - EXCEPTION WHEN raise_exception THEN - IF errmsg IS NOT NULL THEN - -- Something went wrong. Emit the error message. - tok := FALSE; - RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( - errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname - )), '^', ' ', 'gn'); - errmsg := NULL; - END IF; - END; - - -- Restore the sequence. - EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; - PERFORM _set('curr_test', tnumb); - PERFORM _set('failed', tfaild); - - -- Record this test. - RETURN NEXT ok(tok, tests[i]); - IF NOT tok THEN tfaild := tfaild + 1; END IF; - - END LOOP; - - -- Run the shutdown functions. - FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; - - -- Finish up. - FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP - RETURN NEXT tap; - END LOOP; - - -- Clean up and return. - PERFORM _cleanup(); - RETURN; -END; -$$ LANGUAGE plpgsql; - --- runtests( schema, match ) -CREATE OR REPLACE FUNCTION runtests( NAME, TEXT ) -RETURNS SETOF TEXT AS $$ - SELECT * FROM _runner( - findfuncs( $1, '^startup' ), - findfuncs( $1, '^shutdown' ), - findfuncs( $1, '^setup' ), - findfuncs( $1, '^teardown' ), - findfuncs( $1, $2, '^(startup|shutdown|setup|teardown)' ) - ); -$$ LANGUAGE sql; - --- runtests( schema ) -CREATE OR REPLACE FUNCTION runtests( NAME ) -RETURNS SETOF TEXT AS $$ - SELECT * FROM runtests( $1, '^test' ); -$$ LANGUAGE sql; - --- runtests( match ) -CREATE OR REPLACE FUNCTION runtests( TEXT ) -RETURNS SETOF TEXT AS $$ - SELECT * FROM _runner( - findfuncs( '^startup' ), - findfuncs( '^shutdown' ), - findfuncs( '^setup' ), - findfuncs( '^teardown' ), - findfuncs( $1, '^(startup|shutdown|setup|teardown)' ) - ); -$$ LANGUAGE sql; - --- runtests( ) -CREATE OR REPLACE FUNCTION runtests( ) -RETURNS SETOF TEXT AS $$ - SELECT * FROM runtests( '^test' ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _temptable ( TEXT, TEXT ) -RETURNS TEXT AS $$ -BEGIN - EXECUTE 'CREATE TEMP TABLE ' || $2 || ' AS ' || _query($1); - return $2; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION _temptable ( anyarray, TEXT ) -RETURNS TEXT AS $$ -BEGIN - CREATE TEMP TABLE _____coltmp___ AS - SELECT $1[i] - FROM generate_series(array_lower($1, 1), array_upper($1, 1)) s(i); - EXECUTE 'ALTER TABLE _____coltmp___ RENAME TO ' || $2; - return $2; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION _temptypes( TEXT ) -RETURNS TEXT AS $$ - SELECT array_to_string(ARRAY( - SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) - FROM pg_catalog.pg_attribute a - JOIN pg_catalog.pg_class c ON a.attrelid = c.oid - WHERE c.oid = ('pg_temp.' || $1)::pg_catalog.regclass - AND attnum > 0 - AND NOT attisdropped - ORDER BY attnum - ), ','); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _docomp( TEXT, TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - have ALIAS FOR $1; - want ALIAS FOR $2; - extras TEXT[] := '{}'; - missing TEXT[] := '{}'; - res BOOLEAN := TRUE; - msg TEXT := ''; - rec RECORD; -BEGIN - BEGIN - -- Find extra records. - FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 - || 'SELECT * FROM ' || want LOOP - extras := extras || rec::text; - END LOOP; - - -- Find missing records. - FOR rec in EXECUTE 'SELECT * FROM ' || want || ' EXCEPT ' || $4 - || 'SELECT * FROM ' || have LOOP - missing := missing || rec::text; - END LOOP; - - -- Drop the temporary tables. - EXECUTE 'DROP TABLE ' || have; - EXECUTE 'DROP TABLE ' || want; - EXCEPTION WHEN syntax_error OR datatype_mismatch THEN - msg := E'\n' || diag( - E' Columns differ between queries:\n' - || ' have: (' || _temptypes(have) || E')\n' - || ' want: (' || _temptypes(want) || ')' - ); - EXECUTE 'DROP TABLE ' || have; - EXECUTE 'DROP TABLE ' || want; - RETURN ok(FALSE, $3) || msg; - END; - - -- What extra records do we have? - IF extras[1] IS NOT NULL THEN - res := FALSE; - msg := E'\n' || diag( - E' Extra records:\n ' - || array_to_string( extras, E'\n ' ) - ); - END IF; - - -- What missing records do we have? - IF missing[1] IS NOT NULL THEN - res := FALSE; - msg := msg || E'\n' || diag( - E' Missing records:\n ' - || array_to_string( missing, E'\n ' ) - ); - END IF; - - RETURN ok(res, $3) || msg; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _docomp( - _temptable( $1, '__taphave__' ), - _temptable( $2, '__tapwant__' ), - $3, $4 - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _relcomp( TEXT, anyarray, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _docomp( - _temptable( $1, '__taphave__' ), - _temptable( $2, '__tapwant__' ), - $3, $4 - ); -$$ LANGUAGE sql; - --- set_eq( sql, sql, description ) -CREATE OR REPLACE FUNCTION set_eq( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, $3, '' ); -$$ LANGUAGE sql; - --- set_eq( sql, sql ) -CREATE OR REPLACE FUNCTION set_eq( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, NULL::text, '' ); -$$ LANGUAGE sql; - --- set_eq( sql, array, description ) -CREATE OR REPLACE FUNCTION set_eq( TEXT, anyarray, TEXT ) -RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, $3, '' ); -$$ LANGUAGE sql; - --- set_eq( sql, array ) -CREATE OR REPLACE FUNCTION set_eq( TEXT, anyarray ) -RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, NULL::text, '' ); -$$ LANGUAGE sql; - --- bag_eq( sql, sql, description ) -CREATE OR REPLACE FUNCTION bag_eq( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, $3, 'ALL ' ); -$$ LANGUAGE sql; - --- bag_eq( sql, sql ) -CREATE OR REPLACE FUNCTION bag_eq( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, NULL::text, 'ALL ' ); -$$ LANGUAGE sql; - --- bag_eq( sql, array, description ) -CREATE OR REPLACE FUNCTION bag_eq( TEXT, anyarray, TEXT ) -RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, $3, 'ALL ' ); -$$ LANGUAGE sql; - --- bag_eq( sql, array ) -CREATE OR REPLACE FUNCTION bag_eq( TEXT, anyarray ) -RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, NULL::text, 'ALL ' ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _do_ne( TEXT, TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - have ALIAS FOR $1; - want ALIAS FOR $2; - extras TEXT[] := '{}'; - missing TEXT[] := '{}'; - res BOOLEAN := TRUE; - msg TEXT := ''; -BEGIN - BEGIN - -- Find extra records. - EXECUTE 'SELECT EXISTS ( ' - || '( SELECT * FROM ' || have || ' EXCEPT ' || $4 - || ' SELECT * FROM ' || want - || ' ) UNION ( ' - || ' SELECT * FROM ' || want || ' EXCEPT ' || $4 - || ' SELECT * FROM ' || have - || ' ) LIMIT 1 )' INTO res; - - -- Drop the temporary tables. - EXECUTE 'DROP TABLE ' || have; - EXECUTE 'DROP TABLE ' || want; - EXCEPTION WHEN syntax_error OR datatype_mismatch THEN - msg := E'\n' || diag( - E' Columns differ between queries:\n' - || ' have: (' || _temptypes(have) || E')\n' - || ' want: (' || _temptypes(want) || ')' - ); - EXECUTE 'DROP TABLE ' || have; - EXECUTE 'DROP TABLE ' || want; - RETURN ok(FALSE, $3) || msg; - END; - - -- Return the value from the query. - RETURN ok(res, $3); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION _relne( TEXT, TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _do_ne( - _temptable( $1, '__taphave__' ), - _temptable( $2, '__tapwant__' ), - $3, $4 - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _relne( TEXT, anyarray, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _do_ne( - _temptable( $1, '__taphave__' ), - _temptable( $2, '__tapwant__' ), - $3, $4 - ); -$$ LANGUAGE sql; - --- set_ne( sql, sql, description ) -CREATE OR REPLACE FUNCTION set_ne( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _relne( $1, $2, $3, '' ); -$$ LANGUAGE sql; - --- set_ne( sql, sql ) -CREATE OR REPLACE FUNCTION set_ne( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _relne( $1, $2, NULL::text, '' ); -$$ LANGUAGE sql; - --- set_ne( sql, array, description ) -CREATE OR REPLACE FUNCTION set_ne( TEXT, anyarray, TEXT ) -RETURNS TEXT AS $$ - SELECT _relne( $1, $2, $3, '' ); -$$ LANGUAGE sql; - --- set_ne( sql, array ) -CREATE OR REPLACE FUNCTION set_ne( TEXT, anyarray ) -RETURNS TEXT AS $$ - SELECT _relne( $1, $2, NULL::text, '' ); -$$ LANGUAGE sql; - --- bag_ne( sql, sql, description ) -CREATE OR REPLACE FUNCTION bag_ne( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _relne( $1, $2, $3, 'ALL ' ); -$$ LANGUAGE sql; - --- bag_ne( sql, sql ) -CREATE OR REPLACE FUNCTION bag_ne( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _relne( $1, $2, NULL::text, 'ALL ' ); -$$ LANGUAGE sql; - --- bag_ne( sql, array, description ) -CREATE OR REPLACE FUNCTION bag_ne( TEXT, anyarray, TEXT ) -RETURNS TEXT AS $$ - SELECT _relne( $1, $2, $3, 'ALL ' ); -$$ LANGUAGE sql; - --- bag_ne( sql, array ) -CREATE OR REPLACE FUNCTION bag_ne( TEXT, anyarray ) -RETURNS TEXT AS $$ - SELECT _relne( $1, $2, NULL::text, 'ALL ' ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - have TEXT := _temptable( $1, '__taphave__' ); - want TEXT := _temptable( $2, '__tapwant__' ); - results TEXT[] := '{}'; - res BOOLEAN := TRUE; - msg TEXT := ''; - rec RECORD; -BEGIN - BEGIN - -- Find relevant records. - FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 - || ' SELECT * FROM ' || have LOOP - results := results || rec::text; - END LOOP; - - -- Drop the temporary tables. - EXECUTE 'DROP TABLE ' || have; - EXECUTE 'DROP TABLE ' || want; - EXCEPTION WHEN syntax_error OR datatype_mismatch THEN - msg := E'\n' || diag( - E' Columns differ between queries:\n' - || ' have: (' || _temptypes(have) || E')\n' - || ' want: (' || _temptypes(want) || ')' - ); - EXECUTE 'DROP TABLE ' || have; - EXECUTE 'DROP TABLE ' || want; - RETURN ok(FALSE, $3) || msg; - END; - - -- What records do we have? - IF results[1] IS NOT NULL THEN - res := FALSE; - msg := msg || E'\n' || diag( - ' ' || $5 || E' records:\n ' - || array_to_string( results, E'\n ' ) - ); - END IF; - - RETURN ok(res, $3) || msg; -END; -$$ LANGUAGE plpgsql; - --- set_has( sql, sql, description ) -CREATE OR REPLACE FUNCTION set_has( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, $3, 'EXCEPT', 'Missing' ); -$$ LANGUAGE sql; - --- set_has( sql, sql ) -CREATE OR REPLACE FUNCTION set_has( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, NULL::TEXT, 'EXCEPT', 'Missing' ); -$$ LANGUAGE sql; - --- bag_has( sql, sql, description ) -CREATE OR REPLACE FUNCTION bag_has( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, $3, 'EXCEPT ALL', 'Missing' ); -$$ LANGUAGE sql; - --- bag_has( sql, sql ) -CREATE OR REPLACE FUNCTION bag_has( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, NULL::TEXT, 'EXCEPT ALL', 'Missing' ); -$$ LANGUAGE sql; - --- set_hasnt( sql, sql, description ) -CREATE OR REPLACE FUNCTION set_hasnt( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, $3, 'INTERSECT', 'Extra' ); -$$ LANGUAGE sql; - --- set_hasnt( sql, sql ) -CREATE OR REPLACE FUNCTION set_hasnt( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, NULL::TEXT, 'INTERSECT', 'Extra' ); -$$ LANGUAGE sql; - --- bag_hasnt( sql, sql, description ) -CREATE OR REPLACE FUNCTION bag_hasnt( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, $3, 'INTERSECT ALL', 'Extra' ); -$$ LANGUAGE sql; - --- bag_hasnt( sql, sql ) -CREATE OR REPLACE FUNCTION bag_hasnt( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, NULL::TEXT, 'INTERSECT ALL', 'Extra' ); -$$ LANGUAGE sql; - --- results_eq( cursor, cursor, description ) -CREATE OR REPLACE FUNCTION results_eq( refcursor, refcursor, text ) -RETURNS TEXT AS $$ -DECLARE - have ALIAS FOR $1; - want ALIAS FOR $2; - have_rec RECORD; - want_rec RECORD; - have_found BOOLEAN; - want_found BOOLEAN; - rownum INTEGER := 1; - err_msg text := 'details not available in pg <= 9.1'; -BEGIN - FETCH have INTO have_rec; - have_found := FOUND; - FETCH want INTO want_rec; - want_found := FOUND; - WHILE have_found OR want_found LOOP - IF have_rec IS DISTINCT FROM want_rec OR have_found <> want_found THEN - RETURN ok( false, $3 ) || E'\n' || diag( - ' Results differ beginning at row ' || rownum || E':\n' || - ' have: ' || CASE WHEN have_found THEN have_rec::text ELSE 'NULL' END || E'\n' || - ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE 'NULL' END - ); - END IF; - rownum = rownum + 1; - FETCH have INTO have_rec; - have_found := FOUND; - FETCH want INTO want_rec; - want_found := FOUND; - END LOOP; - - RETURN ok( true, $3 ); -EXCEPTION - WHEN datatype_mismatch THEN - GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT; - RETURN ok( false, $3 ) || E'\n' || diag( - E' Number of columns or their types differ between the queries' || - CASE WHEN have_rec::TEXT = want_rec::text THEN '' ELSE E':\n' || - ' have: ' || CASE WHEN have_found THEN have_rec::text ELSE 'NULL' END || E'\n' || - ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE 'NULL' END - END || E'\n ERROR: ' || err_msg - ); -END; -$$ LANGUAGE plpgsql; - --- results_eq( cursor, cursor ) -CREATE OR REPLACE FUNCTION results_eq( refcursor, refcursor ) -RETURNS TEXT AS $$ - SELECT results_eq( $1, $2, NULL::text ); -$$ LANGUAGE sql; - --- results_eq( sql, sql, description ) -CREATE OR REPLACE FUNCTION results_eq( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - have REFCURSOR; - want REFCURSOR; - res TEXT; -BEGIN - OPEN have FOR EXECUTE _query($1); - OPEN want FOR EXECUTE _query($2); - res := results_eq(have, want, $3); - CLOSE have; - CLOSE want; - RETURN res; -END; -$$ LANGUAGE plpgsql; - --- results_eq( sql, sql ) -CREATE OR REPLACE FUNCTION results_eq( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT results_eq( $1, $2, NULL::text ); -$$ LANGUAGE sql; - --- results_eq( sql, array, description ) -CREATE OR REPLACE FUNCTION results_eq( TEXT, anyarray, TEXT ) -RETURNS TEXT AS $$ -DECLARE - have REFCURSOR; - want REFCURSOR; - res TEXT; -BEGIN - OPEN have FOR EXECUTE _query($1); - OPEN want FOR SELECT $2[i] - FROM generate_series(array_lower($2, 1), array_upper($2, 1)) s(i); - res := results_eq(have, want, $3); - CLOSE have; - CLOSE want; - RETURN res; -END; -$$ LANGUAGE plpgsql; - --- results_eq( sql, array ) -CREATE OR REPLACE FUNCTION results_eq( TEXT, anyarray ) -RETURNS TEXT AS $$ - SELECT results_eq( $1, $2, NULL::text ); -$$ LANGUAGE sql; - --- results_eq( sql, cursor, description ) -CREATE OR REPLACE FUNCTION results_eq( TEXT, refcursor, TEXT ) -RETURNS TEXT AS $$ -DECLARE - have REFCURSOR; - res TEXT; -BEGIN - OPEN have FOR EXECUTE _query($1); - res := results_eq(have, $2, $3); - CLOSE have; - RETURN res; -END; -$$ LANGUAGE plpgsql; - --- results_eq( sql, cursor ) -CREATE OR REPLACE FUNCTION results_eq( TEXT, refcursor ) -RETURNS TEXT AS $$ - SELECT results_eq( $1, $2, NULL::text ); -$$ LANGUAGE sql; - --- results_eq( cursor, sql, description ) -CREATE OR REPLACE FUNCTION results_eq( refcursor, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - want REFCURSOR; - res TEXT; -BEGIN - OPEN want FOR EXECUTE _query($2); - res := results_eq($1, want, $3); - CLOSE want; - RETURN res; -END; -$$ LANGUAGE plpgsql; - --- results_eq( cursor, sql ) -CREATE OR REPLACE FUNCTION results_eq( refcursor, TEXT ) -RETURNS TEXT AS $$ - SELECT results_eq( $1, $2, NULL::text ); -$$ LANGUAGE sql; - --- results_eq( cursor, array, description ) -CREATE OR REPLACE FUNCTION results_eq( refcursor, anyarray, TEXT ) -RETURNS TEXT AS $$ -DECLARE - want REFCURSOR; - res TEXT; -BEGIN - OPEN want FOR SELECT $2[i] - FROM generate_series(array_lower($2, 1), array_upper($2, 1)) s(i); - res := results_eq($1, want, $3); - CLOSE want; - RETURN res; -END; -$$ LANGUAGE plpgsql; - --- results_eq( cursor, array ) -CREATE OR REPLACE FUNCTION results_eq( refcursor, anyarray ) -RETURNS TEXT AS $$ - SELECT results_eq( $1, $2, NULL::text ); -$$ LANGUAGE sql; - --- results_ne( cursor, cursor, description ) -CREATE OR REPLACE FUNCTION results_ne( refcursor, refcursor, text ) -RETURNS TEXT AS $$ -DECLARE - have ALIAS FOR $1; - want ALIAS FOR $2; - have_rec RECORD; - want_rec RECORD; - have_found BOOLEAN; - want_found BOOLEAN; - err_msg text := 'details not available in pg <= 9.1'; -BEGIN - FETCH have INTO have_rec; - have_found := FOUND; - FETCH want INTO want_rec; - want_found := FOUND; - WHILE have_found OR want_found LOOP - IF have_rec IS DISTINCT FROM want_rec OR have_found <> want_found THEN - RETURN ok( true, $3 ); - ELSE - FETCH have INTO have_rec; - have_found := FOUND; - FETCH want INTO want_rec; - want_found := FOUND; - END IF; - END LOOP; - RETURN ok( false, $3 ); -EXCEPTION - WHEN datatype_mismatch THEN - GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT; - RETURN ok( false, $3 ) || E'\n' || diag( - E' Number of columns or their types differ between the queries' || - CASE WHEN have_rec::TEXT = want_rec::text THEN '' ELSE E':\n' || - ' have: ' || CASE WHEN have_found THEN have_rec::text ELSE 'NULL' END || E'\n' || - ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE 'NULL' END - END || E'\n ERROR: ' || err_msg - ); -END; -$$ LANGUAGE plpgsql; - --- results_ne( cursor, cursor ) -CREATE OR REPLACE FUNCTION results_ne( refcursor, refcursor ) -RETURNS TEXT AS $$ - SELECT results_ne( $1, $2, NULL::text ); -$$ LANGUAGE sql; - --- results_ne( sql, sql, description ) -CREATE OR REPLACE FUNCTION results_ne( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - have REFCURSOR; - want REFCURSOR; - res TEXT; -BEGIN - OPEN have FOR EXECUTE _query($1); - OPEN want FOR EXECUTE _query($2); - res := results_ne(have, want, $3); - CLOSE have; - CLOSE want; - RETURN res; -END; -$$ LANGUAGE plpgsql; - --- results_ne( sql, sql ) -CREATE OR REPLACE FUNCTION results_ne( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT results_ne( $1, $2, NULL::text ); -$$ LANGUAGE sql; - --- results_ne( sql, array, description ) -CREATE OR REPLACE FUNCTION results_ne( TEXT, anyarray, TEXT ) -RETURNS TEXT AS $$ -DECLARE - have REFCURSOR; - want REFCURSOR; - res TEXT; -BEGIN - OPEN have FOR EXECUTE _query($1); - OPEN want FOR SELECT $2[i] - FROM generate_series(array_lower($2, 1), array_upper($2, 1)) s(i); - res := results_ne(have, want, $3); - CLOSE have; - CLOSE want; - RETURN res; -END; -$$ LANGUAGE plpgsql; - --- results_ne( sql, array ) -CREATE OR REPLACE FUNCTION results_ne( TEXT, anyarray ) -RETURNS TEXT AS $$ - SELECT results_ne( $1, $2, NULL::text ); -$$ LANGUAGE sql; - --- results_ne( sql, cursor, description ) -CREATE OR REPLACE FUNCTION results_ne( TEXT, refcursor, TEXT ) -RETURNS TEXT AS $$ -DECLARE - have REFCURSOR; - res TEXT; -BEGIN - OPEN have FOR EXECUTE _query($1); - res := results_ne(have, $2, $3); - CLOSE have; - RETURN res; -END; -$$ LANGUAGE plpgsql; - --- results_ne( sql, cursor ) -CREATE OR REPLACE FUNCTION results_ne( TEXT, refcursor ) -RETURNS TEXT AS $$ - SELECT results_ne( $1, $2, NULL::text ); -$$ LANGUAGE sql; - --- results_ne( cursor, sql, description ) -CREATE OR REPLACE FUNCTION results_ne( refcursor, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - want REFCURSOR; - res TEXT; -BEGIN - OPEN want FOR EXECUTE _query($2); - res := results_ne($1, want, $3); - CLOSE want; - RETURN res; -END; -$$ LANGUAGE plpgsql; - --- results_ne( cursor, sql ) -CREATE OR REPLACE FUNCTION results_ne( refcursor, TEXT ) -RETURNS TEXT AS $$ - SELECT results_ne( $1, $2, NULL::text ); -$$ LANGUAGE sql; - --- results_ne( cursor, array, description ) -CREATE OR REPLACE FUNCTION results_ne( refcursor, anyarray, TEXT ) -RETURNS TEXT AS $$ -DECLARE - want REFCURSOR; - res TEXT; -BEGIN - OPEN want FOR SELECT $2[i] - FROM generate_series(array_lower($2, 1), array_upper($2, 1)) s(i); - res := results_ne($1, want, $3); - CLOSE want; - RETURN res; -END; -$$ LANGUAGE plpgsql; - --- results_ne( cursor, array ) -CREATE OR REPLACE FUNCTION results_ne( refcursor, anyarray ) -RETURNS TEXT AS $$ - SELECT results_ne( $1, $2, NULL::text ); -$$ LANGUAGE sql; - --- isa_ok( value, regtype, description ) -CREATE OR REPLACE FUNCTION isa_ok( anyelement, regtype, TEXT ) -RETURNS TEXT AS $$ -DECLARE - typeof regtype := pg_typeof($1); -BEGIN - IF typeof = $2 THEN RETURN ok(true, $3 || ' isa ' || $2 ); END IF; - RETURN ok(false, $3 || ' isa ' || $2 ) || E'\n' || - diag(' ' || $3 || ' isn''t a "' || $2 || '" it''s a "' || typeof || '"'); -END; -$$ LANGUAGE plpgsql; - --- isa_ok( value, regtype ) -CREATE OR REPLACE FUNCTION isa_ok( anyelement, regtype ) -RETURNS TEXT AS $$ - SELECT isa_ok($1, $2, 'the value'); -$$ LANGUAGE sql; - --- is_empty( sql, description ) -CREATE OR REPLACE FUNCTION is_empty( TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - extras TEXT[] := '{}'; - res BOOLEAN := TRUE; - msg TEXT := ''; - rec RECORD; -BEGIN - -- Find extra records. - FOR rec in EXECUTE _query($1) LOOP - extras := extras || rec::text; - END LOOP; - - -- What extra records do we have? - IF extras[1] IS NOT NULL THEN - res := FALSE; - msg := E'\n' || diag( - E' Unexpected records:\n ' - || array_to_string( extras, E'\n ' ) - ); - END IF; - - RETURN ok(res, $2) || msg; -END; -$$ LANGUAGE plpgsql; - --- is_empty( sql ) -CREATE OR REPLACE FUNCTION is_empty( TEXT ) -RETURNS TEXT AS $$ - SELECT is_empty( $1, NULL ); -$$ LANGUAGE sql; - --- isnt_empty( sql, description ) -CREATE OR REPLACE FUNCTION isnt_empty( TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - res BOOLEAN := FALSE; - rec RECORD; -BEGIN - -- Find extra records. - FOR rec in EXECUTE _query($1) LOOP - res := TRUE; - EXIT; - END LOOP; - - RETURN ok(res, $2); -END; -$$ LANGUAGE plpgsql; - --- isnt_empty( sql ) -CREATE OR REPLACE FUNCTION isnt_empty( TEXT ) -RETURNS TEXT AS $$ - SELECT isnt_empty( $1, NULL ); -$$ LANGUAGE sql; - --- collect_tap( tap, tap, tap ) -CREATE OR REPLACE FUNCTION collect_tap( VARIADIC text[] ) -RETURNS TEXT AS $$ - SELECT array_to_string($1, E'\n'); -$$ LANGUAGE sql; - --- collect_tap( tap[] ) -CREATE OR REPLACE FUNCTION collect_tap( VARCHAR[] ) -RETURNS TEXT AS $$ - SELECT array_to_string($1, E'\n'); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( $1, $4 ) || CASE WHEN $1 THEN '' ELSE E'\n' || diag( - ' error message: ' || COALESCE( quote_literal($2), 'NULL' ) || - E'\n doesn''t match: ' || COALESCE( quote_literal($3), 'NULL' ) - ) END; -$$ LANGUAGE sql; - --- throws_like ( sql, pattern, description ) -CREATE OR REPLACE FUNCTION throws_like ( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ -BEGIN - EXECUTE _query($1); - RETURN ok( FALSE, $3 ) || E'\n' || diag( ' no exception thrown' ); -EXCEPTION WHEN OTHERS THEN - return _tlike( SQLERRM ~~ $2, SQLERRM, $2, $3 ); -END; -$$ LANGUAGE plpgsql; - --- throws_like ( sql, pattern ) -CREATE OR REPLACE FUNCTION throws_like ( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT throws_like($1, $2, 'Should throw exception like ' || quote_literal($2) ); -$$ LANGUAGE sql; - --- throws_ilike ( sql, pattern, description ) -CREATE OR REPLACE FUNCTION throws_ilike ( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ -BEGIN - EXECUTE _query($1); - RETURN ok( FALSE, $3 ) || E'\n' || diag( ' no exception thrown' ); -EXCEPTION WHEN OTHERS THEN - return _tlike( SQLERRM ~~* $2, SQLERRM, $2, $3 ); -END; -$$ LANGUAGE plpgsql; - --- throws_ilike ( sql, pattern ) -CREATE OR REPLACE FUNCTION throws_ilike ( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT throws_ilike($1, $2, 'Should throw exception like ' || quote_literal($2) ); -$$ LANGUAGE sql; - --- throws_matching ( sql, pattern, description ) -CREATE OR REPLACE FUNCTION throws_matching ( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ -BEGIN - EXECUTE _query($1); - RETURN ok( FALSE, $3 ) || E'\n' || diag( ' no exception thrown' ); -EXCEPTION WHEN OTHERS THEN - return _tlike( SQLERRM ~ $2, SQLERRM, $2, $3 ); -END; -$$ LANGUAGE plpgsql; - --- throws_matching ( sql, pattern ) -CREATE OR REPLACE FUNCTION throws_matching ( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT throws_matching($1, $2, 'Should throw exception matching ' || quote_literal($2) ); -$$ LANGUAGE sql; - --- throws_imatching ( sql, pattern, description ) -CREATE OR REPLACE FUNCTION throws_imatching ( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ -BEGIN - EXECUTE _query($1); - RETURN ok( FALSE, $3 ) || E'\n' || diag( ' no exception thrown' ); -EXCEPTION WHEN OTHERS THEN - return _tlike( SQLERRM ~* $2, SQLERRM, $2, $3 ); -END; -$$ LANGUAGE plpgsql; - --- throws_imatching ( sql, pattern ) -CREATE OR REPLACE FUNCTION throws_imatching ( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT throws_imatching($1, $2, 'Should throw exception matching ' || quote_literal($2) ); -$$ LANGUAGE sql; - --- roles_are( roles[], description ) -CREATE OR REPLACE FUNCTION roles_are( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'roles', - ARRAY( - SELECT rolname - FROM pg_catalog.pg_roles - EXCEPT - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - ), - ARRAY( - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - EXCEPT - SELECT rolname - FROM pg_catalog.pg_roles - ), - $2 - ); -$$ LANGUAGE SQL; - --- roles_are( roles[] ) -CREATE OR REPLACE FUNCTION roles_are( NAME[] ) -RETURNS TEXT AS $$ - SELECT roles_are( $1, 'There should be the correct roles' ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _types_are ( NAME, NAME[], TEXT, CHAR[] ) -RETURNS TEXT AS $$ - SELECT _are( - 'types', - ARRAY( - SELECT t.typname - FROM pg_catalog.pg_type t - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace - WHERE ( - t.typrelid = 0 - OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) - ) - AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid) - AND n.nspname = $1 - AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) - EXCEPT - SELECT _typename($2[i]) - FROM generate_series(1, array_upper($2, 1)) s(i) - ), - ARRAY( - SELECT _typename($2[i]) - FROM generate_series(1, array_upper($2, 1)) s(i) - EXCEPT - SELECT t.typname - FROM pg_catalog.pg_type t - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace - WHERE ( - t.typrelid = 0 - OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) - ) - AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid) - AND n.nspname = $1 - AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) - ), - $3 - ); -$$ LANGUAGE SQL; - --- types_are( schema, types[], description ) -CREATE OR REPLACE FUNCTION types_are ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _types_are( $1, $2, $3, NULL ); -$$ LANGUAGE SQL; - --- types_are( schema, types[] ) -CREATE OR REPLACE FUNCTION types_are ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _types_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct types', NULL ); -$$ LANGUAGE SQL; - --- types_are( types[], description ) -CREATE OR REPLACE FUNCTION _types_are ( NAME[], TEXT, CHAR[] ) -RETURNS TEXT AS $$ - SELECT _are( - 'types', - ARRAY( - SELECT t.typname - FROM pg_catalog.pg_type t - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace - WHERE ( - t.typrelid = 0 - OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) - ) - AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid) - AND n.nspname NOT IN ('pg_catalog', 'information_schema') - AND pg_catalog.pg_type_is_visible(t.oid) - AND t.typtype = ANY( COALESCE($3, ARRAY['b', 'c', 'd', 'p', 'e']) ) - EXCEPT - SELECT _typename($1[i]) - FROM generate_series(1, array_upper($1, 1)) s(i) - ), - ARRAY( - SELECT _typename($1[i]) - FROM generate_series(1, array_upper($1, 1)) s(i) - EXCEPT - SELECT t.typname - FROM pg_catalog.pg_type t - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace - WHERE ( - t.typrelid = 0 - OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) - ) - AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid) - AND n.nspname NOT IN ('pg_catalog', 'information_schema') - AND pg_catalog.pg_type_is_visible(t.oid) - AND t.typtype = ANY( COALESCE($3, ARRAY['b', 'c', 'd', 'p', 'e']) ) - ), - $2 - ); -$$ LANGUAGE SQL; - --- types_are( types[], description ) -CREATE OR REPLACE FUNCTION types_are ( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _types_are( $1, $2, NULL ); -$$ LANGUAGE SQL; - --- types_are( types[] ) -CREATE OR REPLACE FUNCTION types_are ( NAME[] ) -RETURNS TEXT AS $$ - SELECT _types_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct types', NULL ); -$$ LANGUAGE SQL; - --- domains_are( schema, domains[], description ) -CREATE OR REPLACE FUNCTION domains_are ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _types_are( $1, $2, $3, ARRAY['d'] ); -$$ LANGUAGE SQL; - --- domains_are( schema, domains[] ) -CREATE OR REPLACE FUNCTION domains_are ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _types_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct domains', ARRAY['d'] ); -$$ LANGUAGE SQL; - --- domains_are( domains[], description ) -CREATE OR REPLACE FUNCTION domains_are ( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _types_are( $1, $2, ARRAY['d'] ); -$$ LANGUAGE SQL; - --- domains_are( domains[] ) -CREATE OR REPLACE FUNCTION domains_are ( NAME[] ) -RETURNS TEXT AS $$ - SELECT _types_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct domains', ARRAY['d'] ); -$$ LANGUAGE SQL; - --- enums_are( schema, enums[], description ) -CREATE OR REPLACE FUNCTION enums_are ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _types_are( $1, $2, $3, ARRAY['e'] ); -$$ LANGUAGE SQL; - --- enums_are( schema, enums[] ) -CREATE OR REPLACE FUNCTION enums_are ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _types_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct enums', ARRAY['e'] ); -$$ LANGUAGE SQL; - --- enums_are( enums[], description ) -CREATE OR REPLACE FUNCTION enums_are ( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _types_are( $1, $2, ARRAY['e'] ); -$$ LANGUAGE SQL; - --- enums_are( enums[] ) -CREATE OR REPLACE FUNCTION enums_are ( NAME[] ) -RETURNS TEXT AS $$ - SELECT _types_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct enums', ARRAY['e'] ); -$$ LANGUAGE SQL; - --- _dexists( schema, domain ) -CREATE OR REPLACE FUNCTION _dexists ( NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_type t on n.oid = t.typnamespace - WHERE n.nspname = $1 - AND t.typname = $2 - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _dexists ( NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_type t - WHERE t.typname = $1 - AND pg_catalog.pg_type_is_visible(t.oid) - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_dtype( NAME, TEXT, BOOLEAN ) -RETURNS TEXT AS $$ - SELECT CASE WHEN $3 AND pg_catalog.pg_type_is_visible(t.oid) - THEN quote_ident(tn.nspname) || '.' - ELSE '' - END || pg_catalog.format_type(t.oid, t.typtypmod) - FROM pg_catalog.pg_type d - JOIN pg_catalog.pg_namespace dn ON d.typnamespace = dn.oid - JOIN pg_catalog.pg_type t ON d.typbasetype = t.oid - JOIN pg_catalog.pg_namespace tn ON t.typnamespace = tn.oid - WHERE d.typisdefined - AND dn.nspname = $1 - AND d.typname = LOWER($2) - AND d.typtype = 'd' -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _get_dtype( NAME ) -RETURNS TEXT AS $$ - SELECT pg_catalog.format_type(t.oid, t.typtypmod) - FROM pg_catalog.pg_type d - JOIN pg_catalog.pg_type t ON d.typbasetype = t.oid - WHERE d.typisdefined - AND pg_catalog.pg_type_is_visible(d.oid) - AND d.typname = LOWER($1) - AND d.typtype = 'd' -$$ LANGUAGE sql; - --- domain_type_is( schema, domain, schema, type, description ) -CREATE OR REPLACE FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - actual_type TEXT := _get_dtype($1, $2, true); -BEGIN - IF actual_type IS NULL THEN - RETURN fail( $5 ) || E'\n' || diag ( - ' Domain ' || quote_ident($1) || '.' || $2 - || ' does not exist' - ); - END IF; - - IF quote_ident($3) = ANY(current_schemas(true)) THEN - RETURN is( actual_type, quote_ident($3) || '.' || _typename($4), $5); - END IF; - RETURN is( actual_type, _typename(quote_ident($3) || '.' || $4), $5); -END; -$$ LANGUAGE plpgsql; - --- domain_type_is( schema, domain, schema, type ) -CREATE OR REPLACE FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT domain_type_is( - $1, $2, $3, $4, - 'Domain ' || quote_ident($1) || '.' || $2 - || ' should extend type ' || quote_ident($3) || '.' || $4 - ); -$$ LANGUAGE SQL; - --- domain_type_is( schema, domain, type, description ) -CREATE OR REPLACE FUNCTION domain_type_is( NAME, TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - actual_type TEXT := _get_dtype($1, $2, false); -BEGIN - IF actual_type IS NULL THEN - RETURN fail( $4 ) || E'\n' || diag ( - ' Domain ' || quote_ident($1) || '.' || $2 - || ' does not exist' - ); - END IF; - - RETURN is( actual_type, _typename($3), $4 ); -END; -$$ LANGUAGE plpgsql; - --- domain_type_is( schema, domain, type ) -CREATE OR REPLACE FUNCTION domain_type_is( NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT domain_type_is( - $1, $2, $3, - 'Domain ' || quote_ident($1) || '.' || $2 - || ' should extend type ' || $3 - ); -$$ LANGUAGE SQL; - --- domain_type_is( domain, type, description ) -CREATE OR REPLACE FUNCTION domain_type_is( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - actual_type TEXT := _get_dtype($1); -BEGIN - IF actual_type IS NULL THEN - RETURN fail( $3 ) || E'\n' || diag ( - ' Domain ' || $1 || ' does not exist' - ); - END IF; - - RETURN is( actual_type, _typename($2), $3 ); -END; -$$ LANGUAGE plpgsql; - --- domain_type_is( domain, type ) -CREATE OR REPLACE FUNCTION domain_type_is( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT domain_type_is( - $1, $2, - 'Domain ' || $1 || ' should extend type ' || $2 - ); -$$ LANGUAGE SQL; - --- domain_type_isnt( schema, domain, schema, type, description ) -CREATE OR REPLACE FUNCTION domain_type_isnt( NAME, TEXT, NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - actual_type TEXT := _get_dtype($1, $2, true); -BEGIN - IF actual_type IS NULL THEN - RETURN fail( $5 ) || E'\n' || diag ( - ' Domain ' || quote_ident($1) || '.' || $2 - || ' does not exist' - ); - END IF; - - IF quote_ident($3) = ANY(current_schemas(true)) THEN - RETURN isnt( actual_type, quote_ident($3) || '.' || _typename($4), $5); - END IF; - RETURN isnt( actual_type, _typename(quote_ident($3) || '.' || $4), $5); -END; -$$ LANGUAGE plpgsql; - --- domain_type_isnt( schema, domain, schema, type ) -CREATE OR REPLACE FUNCTION domain_type_isnt( NAME, TEXT, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT domain_type_isnt( - $1, $2, $3, $4, - 'Domain ' || quote_ident($1) || '.' || $2 - || ' should not extend type ' || quote_ident($3) || '.' || $4 - ); -$$ LANGUAGE SQL; - --- domain_type_isnt( schema, domain, type, description ) -CREATE OR REPLACE FUNCTION domain_type_isnt( NAME, TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - actual_type TEXT := _get_dtype($1, $2, false); -BEGIN - IF actual_type IS NULL THEN - RETURN fail( $4 ) || E'\n' || diag ( - ' Domain ' || quote_ident($1) || '.' || $2 - || ' does not exist' - ); - END IF; - - RETURN isnt( actual_type, _typename($3), $4 ); -END; -$$ LANGUAGE plpgsql; - --- domain_type_isnt( schema, domain, type ) -CREATE OR REPLACE FUNCTION domain_type_isnt( NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT domain_type_isnt( - $1, $2, $3, - 'Domain ' || quote_ident($1) || '.' || $2 - || ' should not extend type ' || $3 - ); -$$ LANGUAGE SQL; - --- domain_type_isnt( domain, type, description ) -CREATE OR REPLACE FUNCTION domain_type_isnt( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - actual_type TEXT := _get_dtype($1); -BEGIN - IF actual_type IS NULL THEN - RETURN fail( $3 ) || E'\n' || diag ( - ' Domain ' || $1 || ' does not exist' - ); - END IF; - - RETURN isnt( actual_type, _typename($2), $3 ); -END; -$$ LANGUAGE plpgsql; - --- domain_type_isnt( domain, type ) -CREATE OR REPLACE FUNCTION domain_type_isnt( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT domain_type_isnt( - $1, $2, - 'Domain ' || $1 || ' should not extend type ' || $2 - ); -$$ LANGUAGE SQL; - --- row_eq( sql, record, description ) -CREATE OR REPLACE FUNCTION row_eq( TEXT, anyelement, TEXT ) -RETURNS TEXT AS $$ -DECLARE - rec RECORD; -BEGIN - EXECUTE _query($1) INTO rec; - IF NOT rec IS DISTINCT FROM $2 THEN RETURN ok(true, $3); END IF; - RETURN ok(false, $3 ) || E'\n' || diag( - ' have: ' || CASE WHEN rec IS NULL THEN 'NULL' ELSE rec::text END || - E'\n want: ' || CASE WHEN $2 IS NULL THEN 'NULL' ELSE $2::text END - ); -END; -$$ LANGUAGE plpgsql; - --- row_eq( sql, record ) -CREATE OR REPLACE FUNCTION row_eq( TEXT, anyelement ) -RETURNS TEXT AS $$ - SELECT row_eq($1, $2, NULL ); -$$ LANGUAGE sql; - --- triggers_are( schema, table, triggers[], description ) -CREATE OR REPLACE FUNCTION triggers_are( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'triggers', - ARRAY( - SELECT t.tgname - FROM pg_catalog.pg_trigger t - JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid - JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE n.nspname = $1 - AND c.relname = $2 - AND NOT t.tgisinternal - EXCEPT - SELECT $3[i] - FROM generate_series(1, array_upper($3, 1)) s(i) - ), - ARRAY( - SELECT $3[i] - FROM generate_series(1, array_upper($3, 1)) s(i) - EXCEPT - SELECT t.tgname - FROM pg_catalog.pg_trigger t - JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid - JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE n.nspname = $1 - AND c.relname = $2 - AND NOT t.tgisinternal - ), - $4 - ); -$$ LANGUAGE SQL; - --- triggers_are( schema, table, triggers[] ) -CREATE OR REPLACE FUNCTION triggers_are( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT triggers_are( $1, $2, $3, 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct triggers' ); -$$ LANGUAGE SQL; - --- triggers_are( table, triggers[], description ) -CREATE OR REPLACE FUNCTION triggers_are( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'triggers', - ARRAY( - SELECT t.tgname - FROM pg_catalog.pg_trigger t - JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid - JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE c.relname = $1 - AND n.nspname NOT IN ('pg_catalog', 'information_schema') - AND NOT t.tgisinternal - EXCEPT - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - ), - ARRAY( - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - EXCEPT - SELECT t.tgname - FROM pg_catalog.pg_trigger t - JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid - JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - AND n.nspname NOT IN ('pg_catalog', 'information_schema') - AND NOT t.tgisinternal - ), - $3 - ); -$$ LANGUAGE SQL; - --- triggers_are( table, triggers[] ) -CREATE OR REPLACE FUNCTION triggers_are( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT triggers_are( $1, $2, 'Table ' || quote_ident($1) || ' should have the correct triggers' ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _areni ( text, text[], text[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - what ALIAS FOR $1; - extras ALIAS FOR $2; - missing ALIAS FOR $3; - descr ALIAS FOR $4; - msg TEXT := ''; - res BOOLEAN := TRUE; -BEGIN - IF extras[1] IS NOT NULL THEN - res = FALSE; - msg := E'\n' || diag( - ' Extra ' || what || E':\n ' - || _array_to_sorted_string( extras, E'\n ' ) - ); - END IF; - IF missing[1] IS NOT NULL THEN - res = FALSE; - msg := msg || E'\n' || diag( - ' Missing ' || what || E':\n ' - || _array_to_sorted_string( missing, E'\n ' ) - ); - END IF; - - RETURN ok(res, descr) || msg; -END; -$$ LANGUAGE plpgsql; - --- casts_are( casts[], description ) -CREATE OR REPLACE FUNCTION casts_are ( TEXT[], TEXT ) -RETURNS TEXT AS $$ - SELECT _areni( - 'casts', - ARRAY( - SELECT pg_catalog.format_type(castsource, NULL) - || ' AS ' || pg_catalog.format_type(casttarget, NULL) - FROM pg_catalog.pg_cast c - EXCEPT - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - ), - ARRAY( - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - EXCEPT - SELECT pg_catalog.format_type(castsource, NULL) - || ' AS ' || pg_catalog.format_type(casttarget, NULL) - FROM pg_catalog.pg_cast c - ), - $2 - ); -$$ LANGUAGE sql; - --- casts_are( casts[] ) -CREATE OR REPLACE FUNCTION casts_are ( TEXT[] ) -RETURNS TEXT AS $$ - SELECT casts_are( $1, 'There should be the correct casts'); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) -RETURNS TEXT AS $$ - SELECT $1 || substring($2::regoperator::text, '[(][^)]+[)]$') -$$ LANGUAGE SQL; - --- operators_are( schema, operators[], description ) -CREATE OR REPLACE FUNCTION operators_are( NAME, TEXT[], TEXT ) -RETURNS TEXT AS $$ - SELECT _areni( - 'operators', - ARRAY( - SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype - FROM pg_catalog.pg_operator o - JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid - WHERE n.nspname = $1 - EXCEPT - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - ), - ARRAY( - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - EXCEPT - SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype - FROM pg_catalog.pg_operator o - JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid - WHERE n.nspname = $1 - ), - $3 - ); -$$ LANGUAGE SQL; - --- operators_are( schema, operators[] ) -CREATE OR REPLACE FUNCTION operators_are ( NAME, TEXT[] ) -RETURNS TEXT AS $$ - SELECT operators_are($1, $2, 'Schema ' || quote_ident($1) || ' should have the correct operators' ); -$$ LANGUAGE SQL; - --- operators_are( operators[], description ) -CREATE OR REPLACE FUNCTION operators_are( TEXT[], TEXT ) -RETURNS TEXT AS $$ - SELECT _areni( - 'operators', - ARRAY( - SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype - FROM pg_catalog.pg_operator o - JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid - WHERE pg_catalog.pg_operator_is_visible(o.oid) - AND n.nspname NOT IN ('pg_catalog', 'information_schema') - EXCEPT - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - ), - ARRAY( - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - EXCEPT - SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype - FROM pg_catalog.pg_operator o - JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid - WHERE pg_catalog.pg_operator_is_visible(o.oid) - AND n.nspname NOT IN ('pg_catalog', 'information_schema') - ), - $2 - ); -$$ LANGUAGE SQL; - --- operators_are( operators[] ) -CREATE OR REPLACE FUNCTION operators_are ( TEXT[] ) -RETURNS TEXT AS $$ - SELECT operators_are($1, 'There should be the correct operators') -$$ LANGUAGE SQL; - --- columns_are( schema, table, columns[], description ) -CREATE OR REPLACE FUNCTION columns_are( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'columns', - ARRAY( - SELECT a.attname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid - WHERE n.nspname = $1 - AND c.relname = $2 - AND a.attnum > 0 - AND NOT a.attisdropped - EXCEPT - SELECT $3[i] - FROM generate_series(1, array_upper($3, 1)) s(i) - ), - ARRAY( - SELECT $3[i] - FROM generate_series(1, array_upper($3, 1)) s(i) - EXCEPT - SELECT a.attname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid - WHERE n.nspname = $1 - AND c.relname = $2 - AND a.attnum > 0 - AND NOT a.attisdropped - ), - $4 - ); -$$ LANGUAGE SQL; - --- columns_are( schema, table, columns[] ) -CREATE OR REPLACE FUNCTION columns_are( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT columns_are( $1, $2, $3, 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct columns' ); -$$ LANGUAGE SQL; - --- columns_are( table, columns[], description ) -CREATE OR REPLACE FUNCTION columns_are( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'columns', - ARRAY( - SELECT a.attname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid - WHERE n.nspname NOT IN ('pg_catalog', 'information_schema') - AND pg_catalog.pg_table_is_visible(c.oid) - AND c.relname = $1 - AND a.attnum > 0 - AND NOT a.attisdropped - EXCEPT - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - ), - ARRAY( - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - EXCEPT - SELECT a.attname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid - WHERE n.nspname NOT IN ('pg_catalog', 'information_schema') - AND pg_catalog.pg_table_is_visible(c.oid) - AND c.relname = $1 - AND a.attnum > 0 - AND NOT a.attisdropped - ), - $3 - ); -$$ LANGUAGE SQL; - --- columns_are( table, columns[] ) -CREATE OR REPLACE FUNCTION columns_are( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT columns_are( $1, $2, 'Table ' || quote_ident($1) || ' should have the correct columns' ); -$$ LANGUAGE SQL; - --- _get_db_owner( dbname ) -CREATE OR REPLACE FUNCTION _get_db_owner( NAME ) -RETURNS NAME AS $$ - SELECT pg_catalog.pg_get_userbyid(datdba) - FROM pg_catalog.pg_database - WHERE datname = $1; -$$ LANGUAGE SQL; - --- db_owner_is ( dbname, user, description ) -CREATE OR REPLACE FUNCTION db_owner_is ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - dbowner NAME := _get_db_owner($1); -BEGIN - -- Make sure the database exists. - IF dbowner IS NULL THEN - RETURN ok(FALSE, $3) || E'\n' || diag( - E' Database ' || quote_ident($1) || ' does not exist' - ); - END IF; - - RETURN is(dbowner, $2, $3); -END; -$$ LANGUAGE plpgsql; - --- db_owner_is ( dbname, user ) -CREATE OR REPLACE FUNCTION db_owner_is ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT db_owner_is( - $1, $2, - 'Database ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) - ); -$$ LANGUAGE sql; - --- _get_schema_owner( schema ) -CREATE OR REPLACE FUNCTION _get_schema_owner( NAME ) -RETURNS NAME AS $$ - SELECT pg_catalog.pg_get_userbyid(nspowner) - FROM pg_catalog.pg_namespace - WHERE nspname = $1; -$$ LANGUAGE SQL; - --- schema_owner_is ( schema, user, description ) -CREATE OR REPLACE FUNCTION schema_owner_is ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_schema_owner($1); -BEGIN - -- Make sure the schema exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $3) || E'\n' || diag( - E' Schema ' || quote_ident($1) || ' does not exist' - ); - END IF; - - RETURN is(owner, $2, $3); -END; -$$ LANGUAGE plpgsql; - --- schema_owner_is ( schema, user ) -CREATE OR REPLACE FUNCTION schema_owner_is ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT schema_owner_is( - $1, $2, - 'Schema ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _get_rel_owner ( NAME, NAME ) -RETURNS NAME AS $$ - SELECT pg_catalog.pg_get_userbyid(c.relowner) - FROM pg_catalog.pg_class c - JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE n.nspname = $1 - AND c.relname = $2 -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_rel_owner ( NAME ) -RETURNS NAME AS $$ - SELECT pg_catalog.pg_get_userbyid(c.relowner) - FROM pg_catalog.pg_class c - WHERE c.relname = $1 - AND pg_catalog.pg_table_is_visible(c.oid) -$$ LANGUAGE SQL; - --- relation_owner_is ( schema, relation, user, description ) -CREATE OR REPLACE FUNCTION relation_owner_is ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_rel_owner($1, $2); -BEGIN - -- Make sure the relation exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - E' Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' - ); - END IF; - - RETURN is(owner, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- relation_owner_is ( schema, relation, user ) -CREATE OR REPLACE FUNCTION relation_owner_is ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT relation_owner_is( - $1, $2, $3, - 'Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) - ); -$$ LANGUAGE sql; - --- relation_owner_is ( relation, user, description ) -CREATE OR REPLACE FUNCTION relation_owner_is ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_rel_owner($1); -BEGIN - -- Make sure the relation exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $3) || E'\n' || diag( - E' Relation ' || quote_ident($1) || ' does not exist' - ); - END IF; - - RETURN is(owner, $2, $3); -END; -$$ LANGUAGE plpgsql; - --- relation_owner_is ( relation, user ) -CREATE OR REPLACE FUNCTION relation_owner_is ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT relation_owner_is( - $1, $2, - 'Relation ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _get_rel_owner ( CHAR[], NAME, NAME ) -RETURNS NAME AS $$ - SELECT pg_catalog.pg_get_userbyid(c.relowner) - FROM pg_catalog.pg_class c - JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE c.relkind = ANY($1) - AND n.nspname = $2 - AND c.relname = $3 -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_rel_owner ( CHAR[], NAME ) -RETURNS NAME AS $$ - SELECT pg_catalog.pg_get_userbyid(c.relowner) - FROM pg_catalog.pg_class c - WHERE c.relkind = ANY($1) - AND c.relname = $2 - AND pg_catalog.pg_table_is_visible(c.oid) -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_rel_owner ( CHAR, NAME, NAME ) -RETURNS NAME AS $$ - SELECT _get_rel_owner(ARRAY[$1], $2, $3); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_rel_owner ( CHAR, NAME ) -RETURNS NAME AS $$ - SELECT _get_rel_owner(ARRAY[$1], $2); -$$ LANGUAGE SQL; - --- table_owner_is ( schema, table, user, description ) -CREATE OR REPLACE FUNCTION table_owner_is ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_rel_owner('{r,p}'::char[], $1, $2); -BEGIN - -- Make sure the table exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - E' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' - ); - END IF; - - RETURN is(owner, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- table_owner_is ( schema, table, user ) -CREATE OR REPLACE FUNCTION table_owner_is ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT table_owner_is( - $1, $2, $3, - 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) - ); -$$ LANGUAGE sql; - --- table_owner_is ( table, user, description ) -CREATE OR REPLACE FUNCTION table_owner_is ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_rel_owner('{r,p}'::char[], $1); -BEGIN - -- Make sure the table exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $3) || E'\n' || diag( - E' Table ' || quote_ident($1) || ' does not exist' - ); - END IF; - - RETURN is(owner, $2, $3); -END; -$$ LANGUAGE plpgsql; - --- table_owner_is ( table, user ) -CREATE OR REPLACE FUNCTION table_owner_is ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT table_owner_is( - $1, $2, - 'Table ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) - ); -$$ LANGUAGE sql; - --- view_owner_is ( schema, view, user, description ) -CREATE OR REPLACE FUNCTION view_owner_is ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_rel_owner('v'::char, $1, $2); -BEGIN - -- Make sure the view exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - E' View ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' - ); - END IF; - - RETURN is(owner, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- view_owner_is ( schema, view, user ) -CREATE OR REPLACE FUNCTION view_owner_is ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT view_owner_is( - $1, $2, $3, - 'View ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) - ); -$$ LANGUAGE sql; - --- view_owner_is ( view, user, description ) -CREATE OR REPLACE FUNCTION view_owner_is ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_rel_owner('v'::char, $1); -BEGIN - -- Make sure the view exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $3) || E'\n' || diag( - E' View ' || quote_ident($1) || ' does not exist' - ); - END IF; - - RETURN is(owner, $2, $3); -END; -$$ LANGUAGE plpgsql; - --- view_owner_is ( view, user ) -CREATE OR REPLACE FUNCTION view_owner_is ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT view_owner_is( - $1, $2, - 'View ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) - ); -$$ LANGUAGE sql; - --- sequence_owner_is ( schema, sequence, user, description ) -CREATE OR REPLACE FUNCTION sequence_owner_is ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_rel_owner('S'::char, $1, $2); -BEGIN - -- Make sure the sequence exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - E' Sequence ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' - ); - END IF; - - RETURN is(owner, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- sequence_owner_is ( schema, sequence, user ) -CREATE OR REPLACE FUNCTION sequence_owner_is ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT sequence_owner_is( - $1, $2, $3, - 'Sequence ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) - ); -$$ LANGUAGE sql; - --- sequence_owner_is ( sequence, user, description ) -CREATE OR REPLACE FUNCTION sequence_owner_is ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_rel_owner('S'::char, $1); -BEGIN - -- Make sure the sequence exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $3) || E'\n' || diag( - E' Sequence ' || quote_ident($1) || ' does not exist' - ); - END IF; - - RETURN is(owner, $2, $3); -END; -$$ LANGUAGE plpgsql; - --- sequence_owner_is ( sequence, user ) -CREATE OR REPLACE FUNCTION sequence_owner_is ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT sequence_owner_is( - $1, $2, - 'Sequence ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) - ); -$$ LANGUAGE sql; - --- composite_owner_is ( schema, composite, user, description ) -CREATE OR REPLACE FUNCTION composite_owner_is ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_rel_owner('c'::char, $1, $2); -BEGIN - -- Make sure the composite exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - E' Composite type ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' - ); - END IF; - - RETURN is(owner, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- composite_owner_is ( schema, composite, user ) -CREATE OR REPLACE FUNCTION composite_owner_is ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT composite_owner_is( - $1, $2, $3, - 'Composite type ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) - ); -$$ LANGUAGE sql; - --- composite_owner_is ( composite, user, description ) -CREATE OR REPLACE FUNCTION composite_owner_is ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_rel_owner('c'::char, $1); -BEGIN - -- Make sure the composite exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $3) || E'\n' || diag( - E' Composite type ' || quote_ident($1) || ' does not exist' - ); - END IF; - - RETURN is(owner, $2, $3); -END; -$$ LANGUAGE plpgsql; - --- composite_owner_is ( composite, user ) -CREATE OR REPLACE FUNCTION composite_owner_is ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT composite_owner_is( - $1, $2, - 'Composite type ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) - ); -$$ LANGUAGE sql; - --- foreign_table_owner_is ( schema, table, user, description ) -CREATE OR REPLACE FUNCTION foreign_table_owner_is ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_rel_owner('f'::char, $1, $2); -BEGIN - -- Make sure the table exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - E' Foreign table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' - ); - END IF; - - RETURN is(owner, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- foreign_table_owner_is ( schema, table, user ) -CREATE OR REPLACE FUNCTION foreign_table_owner_is ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT foreign_table_owner_is( - $1, $2, $3, - 'Foreign table ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) - ); -$$ LANGUAGE sql; - --- foreign_table_owner_is ( table, user, description ) -CREATE OR REPLACE FUNCTION foreign_table_owner_is ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_rel_owner('f'::char, $1); -BEGIN - -- Make sure the table exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $3) || E'\n' || diag( - E' Foreign table ' || quote_ident($1) || ' does not exist' - ); - END IF; - - RETURN is(owner, $2, $3); -END; -$$ LANGUAGE plpgsql; - --- foreign_table_owner_is ( table, user ) -CREATE OR REPLACE FUNCTION foreign_table_owner_is ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT foreign_table_owner_is( - $1, $2, - 'Foreign table ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _get_func_owner ( NAME, NAME, NAME[] ) -RETURNS NAME AS $$ - SELECT owner - FROM tap_funky - WHERE schema = $1 - AND name = $2 - AND args = _funkargs($3) -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_func_owner ( NAME, NAME[] ) -RETURNS NAME AS $$ - SELECT owner - FROM tap_funky - WHERE name = $1 - AND args = _funkargs($2) - AND is_visible -$$ LANGUAGE SQL; - --- function_owner_is( schema, function, args[], user, description ) -CREATE OR REPLACE FUNCTION function_owner_is ( NAME, NAME, NAME[], NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_func_owner($1, $2, $3); -BEGIN - -- Make sure the function exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $5) || E'\n' || diag( - E' Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') does not exist' - ); - END IF; - - RETURN is(owner, $4, $5); -END; -$$ LANGUAGE plpgsql; - --- function_owner_is( schema, function, args[], user ) -CREATE OR REPLACE FUNCTION function_owner_is( NAME, NAME, NAME[], NAME ) -RETURNS TEXT AS $$ - SELECT function_owner_is( - $1, $2, $3, $4, - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should be owned by ' || quote_ident($4) - ); -$$ LANGUAGE sql; - --- function_owner_is( function, args[], user, description ) -CREATE OR REPLACE FUNCTION function_owner_is ( NAME, NAME[], NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_func_owner($1, $2); -BEGIN - -- Make sure the function exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - E' Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') does not exist' - ); - END IF; - - RETURN is(owner, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- function_owner_is( function, args[], user ) -CREATE OR REPLACE FUNCTION function_owner_is( NAME, NAME[], NAME ) -RETURNS TEXT AS $$ - SELECT function_owner_is( - $1, $2, $3, - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should be owned by ' || quote_ident($3) - ); -$$ LANGUAGE sql; - --- _get_tablespace_owner( tablespace ) -CREATE OR REPLACE FUNCTION _get_tablespace_owner( NAME ) -RETURNS NAME AS $$ - SELECT pg_catalog.pg_get_userbyid(spcowner) - FROM pg_catalog.pg_tablespace - WHERE spcname = $1; -$$ LANGUAGE SQL; - --- tablespace_owner_is ( tablespace, user, description ) -CREATE OR REPLACE FUNCTION tablespace_owner_is ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_tablespace_owner($1); -BEGIN - -- Make sure the tablespace exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $3) || E'\n' || diag( - E' Tablespace ' || quote_ident($1) || ' does not exist' - ); - END IF; - - RETURN is(owner, $2, $3); -END; -$$ LANGUAGE plpgsql; - --- tablespace_owner_is ( tablespace, user ) -CREATE OR REPLACE FUNCTION tablespace_owner_is ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT tablespace_owner_is( - $1, $2, - 'Tablespace ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _get_index_owner( NAME, NAME, NAME ) -RETURNS NAME AS $$ - SELECT pg_catalog.pg_get_userbyid(ci.relowner) - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace - WHERE n.nspname = $1 - AND ct.relname = $2 - AND ci.relname = $3; -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _get_index_owner( NAME, NAME ) -RETURNS NAME AS $$ - SELECT pg_catalog.pg_get_userbyid(ci.relowner) - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid - JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid - WHERE ct.relname = $1 - AND ci.relname = $2 - AND pg_catalog.pg_table_is_visible(ct.oid); -$$ LANGUAGE sql; - --- index_owner_is ( schema, table, index, user, description ) -CREATE OR REPLACE FUNCTION index_owner_is ( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_index_owner($1, $2, $3); -BEGIN - -- Make sure the index exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $5) || E'\n' || diag( - E' Index ' || quote_ident($3) || ' ON ' - || quote_ident($1) || '.' || quote_ident($2) || ' not found' - ); - END IF; - - RETURN is(owner, $4, $5); -END; -$$ LANGUAGE plpgsql; - --- index_owner_is ( schema, table, index, user ) -CREATE OR REPLACE FUNCTION index_owner_is ( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT index_owner_is( - $1, $2, $3, $4, - 'Index ' || quote_ident($3) || ' ON ' - || quote_ident($1) || '.' || quote_ident($2) - || ' should be owned by ' || quote_ident($4) - ); -$$ LANGUAGE sql; - --- index_owner_is ( table, index, user, description ) -CREATE OR REPLACE FUNCTION index_owner_is ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_index_owner($1, $2); -BEGIN - -- Make sure the index exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - E' Index ' || quote_ident($2) || ' ON ' || quote_ident($1) || ' not found' - ); - END IF; - - RETURN is(owner, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- index_owner_is ( table, index, user ) -CREATE OR REPLACE FUNCTION index_owner_is ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT index_owner_is( - $1, $2, $3, - 'Index ' || quote_ident($2) || ' ON ' - || quote_ident($1) || ' should be owned by ' || quote_ident($3) - ); -$$ LANGUAGE sql; - --- _get_language_owner( language ) -CREATE OR REPLACE FUNCTION _get_language_owner( NAME ) -RETURNS NAME AS $$ - SELECT pg_catalog.pg_get_userbyid(lanowner) - FROM pg_catalog.pg_language - WHERE lanname = $1; -$$ LANGUAGE SQL; - --- language_owner_is ( language, user, description ) -CREATE OR REPLACE FUNCTION language_owner_is ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_language_owner($1); -BEGIN - -- Make sure the language exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $3) || E'\n' || diag( - E' Language ' || quote_ident($1) || ' does not exist' - ); - END IF; - - RETURN is(owner, $2, $3); -END; -$$ LANGUAGE plpgsql; - --- language_owner_is ( language, user ) -CREATE OR REPLACE FUNCTION language_owner_is ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT language_owner_is( - $1, $2, - 'Language ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _get_opclass_owner ( NAME, NAME ) -RETURNS NAME AS $$ - SELECT pg_catalog.pg_get_userbyid(opcowner) - FROM pg_catalog.pg_opclass oc - JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid - WHERE n.nspname = $1 - AND opcname = $2; -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_opclass_owner ( NAME ) -RETURNS NAME AS $$ - SELECT pg_catalog.pg_get_userbyid(opcowner) - FROM pg_catalog.pg_opclass - WHERE opcname = $1 - AND pg_catalog.pg_opclass_is_visible(oid); -$$ LANGUAGE SQL; - --- opclass_owner_is( schema, opclass, user, description ) -CREATE OR REPLACE FUNCTION opclass_owner_is ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_opclass_owner($1, $2); -BEGIN - -- Make sure the opclass exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - E' Operator class ' || quote_ident($1) || '.' || quote_ident($2) - || ' not found' - ); - END IF; - - RETURN is(owner, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- opclass_owner_is( schema, opclass, user ) -CREATE OR REPLACE FUNCTION opclass_owner_is( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT opclass_owner_is( - $1, $2, $3, - 'Operator class ' || quote_ident($1) || '.' || quote_ident($2) || - ' should be owned by ' || quote_ident($3) - ); -$$ LANGUAGE sql; - --- opclass_owner_is( opclass, user, description ) -CREATE OR REPLACE FUNCTION opclass_owner_is ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_opclass_owner($1); -BEGIN - -- Make sure the opclass exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $3) || E'\n' || diag( - E' Operator class ' || quote_ident($1) || ' not found' - ); - END IF; - - RETURN is(owner, $2, $3); -END; -$$ LANGUAGE plpgsql; - --- opclass_owner_is( opclass, user ) -CREATE OR REPLACE FUNCTION opclass_owner_is( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT opclass_owner_is( - $1, $2, - 'Operator class ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _get_type_owner ( NAME, NAME ) -RETURNS NAME AS $$ - SELECT pg_catalog.pg_get_userbyid(t.typowner) - FROM pg_catalog.pg_type t - JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace - WHERE n.nspname = $1 - AND t.typname = $2 -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_type_owner ( NAME ) -RETURNS NAME AS $$ - SELECT pg_catalog.pg_get_userbyid(typowner) - FROM pg_catalog.pg_type - WHERE typname = $1 - AND pg_catalog.pg_type_is_visible(oid) -$$ LANGUAGE SQL; - --- type_owner_is ( schema, type, user, description ) -CREATE OR REPLACE FUNCTION type_owner_is ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_type_owner($1, $2); -BEGIN - -- Make sure the type exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - E' Type ' || quote_ident($1) || '.' || quote_ident($2) || ' not found' - ); - END IF; - - RETURN is(owner, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- type_owner_is ( schema, type, user ) -CREATE OR REPLACE FUNCTION type_owner_is ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT type_owner_is( - $1, $2, $3, - 'Type ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) - ); -$$ LANGUAGE sql; - --- type_owner_is ( type, user, description ) -CREATE OR REPLACE FUNCTION type_owner_is ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_type_owner($1); -BEGIN - -- Make sure the type exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $3) || E'\n' || diag( - E' Type ' || quote_ident($1) || ' not found' - ); - END IF; - - RETURN is(owner, $2, $3); -END; -$$ LANGUAGE plpgsql; - --- type_owner_is ( type, user ) -CREATE OR REPLACE FUNCTION type_owner_is ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT type_owner_is( - $1, $2, - 'Type ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _assets_are ( text, text[], text[], TEXT ) -RETURNS TEXT AS $$ - SELECT _areni( - $1, - ARRAY( - SELECT UPPER($2[i]) AS thing - FROM generate_series(1, array_upper($2, 1)) s(i) - EXCEPT - SELECT $3[i] - FROM generate_series(1, array_upper($3, 1)) s(i) - ORDER BY thing - ), - ARRAY( - SELECT $3[i] AS thing - FROM generate_series(1, array_upper($3, 1)) s(i) - EXCEPT - SELECT UPPER($2[i]) - FROM generate_series(1, array_upper($2, 1)) s(i) - ORDER BY thing - ), - $4 - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_table_privs(NAME, TEXT) -RETURNS TEXT[] AS $$ -DECLARE - privs TEXT[] := _table_privs(); - grants TEXT[] := '{}'; -BEGIN - FOR i IN 1..array_upper(privs, 1) LOOP - BEGIN - IF pg_catalog.has_table_privilege($1, $2, privs[i]) THEN - grants := grants || privs[i]; - END IF; - EXCEPTION WHEN undefined_table THEN - -- Not a valid table name. - RETURN '{undefined_table}'; - WHEN undefined_object THEN - -- Not a valid role. - RETURN '{undefined_role}'; - WHEN invalid_parameter_value THEN - -- Not a valid permission on this version of PostgreSQL; ignore; - END; - END LOOP; - RETURN grants; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION _table_privs() -RETURNS NAME[] AS $$ -DECLARE - pgversion INTEGER := pg_version_num(); -BEGIN - IF pgversion < 80200 THEN RETURN ARRAY[ - 'DELETE', 'INSERT', 'REFERENCES', 'RULE', 'SELECT', 'TRIGGER', 'UPDATE' - ]; - ELSIF pgversion < 80400 THEN RETURN ARRAY[ - 'DELETE', 'INSERT', 'REFERENCES', 'SELECT', 'TRIGGER', 'UPDATE' - ]; - ELSE RETURN ARRAY[ - 'DELETE', 'INSERT', 'REFERENCES', 'SELECT', 'TRIGGER', 'TRUNCATE', 'UPDATE' - ]; - END IF; -END; -$$ language plpgsql; - --- table_privs_are ( schema, table, user, privileges[], description ) -CREATE OR REPLACE FUNCTION table_privs_are ( NAME, NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - grants TEXT[] := _get_table_privs( $3, quote_ident($1) || '.' || quote_ident($2) ); -BEGIN - IF grants[1] = 'undefined_table' THEN - RETURN ok(FALSE, $5) || E'\n' || diag( - ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' - ); - ELSIF grants[1] = 'undefined_role' THEN - RETURN ok(FALSE, $5) || E'\n' || diag( - ' Role ' || quote_ident($3) || ' does not exist' - ); - END IF; - RETURN _assets_are('privileges', grants, $4, $5); -END; -$$ LANGUAGE plpgsql; - --- table_privs_are ( schema, table, user, privileges[] ) -CREATE OR REPLACE FUNCTION table_privs_are ( NAME, NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT table_privs_are( - $1, $2, $3, $4, - 'Role ' || quote_ident($3) || ' should be granted ' - || CASE WHEN $4[1] IS NULL THEN 'no privileges' ELSE array_to_string($4, ', ') END - || ' on table ' || quote_ident($1) || '.' || quote_ident($2) - ); -$$ LANGUAGE SQL; - --- table_privs_are ( table, user, privileges[], description ) -CREATE OR REPLACE FUNCTION table_privs_are ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - grants TEXT[] := _get_table_privs( $2, quote_ident($1) ); -BEGIN - IF grants[1] = 'undefined_table' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' - ); - ELSIF grants[1] = 'undefined_role' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Role ' || quote_ident($2) || ' does not exist' - ); - END IF; - RETURN _assets_are('privileges', grants, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- table_privs_are ( table, user, privileges[] ) -CREATE OR REPLACE FUNCTION table_privs_are ( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT table_privs_are( - $1, $2, $3, - 'Role ' || quote_ident($2) || ' should be granted ' - || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END - || ' on table ' || quote_ident($1) - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _db_privs() -RETURNS NAME[] AS $$ -DECLARE - pgversion INTEGER := pg_version_num(); -BEGIN - IF pgversion < 80200 THEN - RETURN ARRAY['CREATE', 'TEMPORARY']; - ELSE - RETURN ARRAY['CREATE', 'CONNECT', 'TEMPORARY']; - END IF; -END; -$$ language plpgsql; - -CREATE OR REPLACE FUNCTION _get_db_privs(NAME, TEXT) -RETURNS TEXT[] AS $$ -DECLARE - privs TEXT[] := _db_privs(); - grants TEXT[] := '{}'; -BEGIN - FOR i IN 1..array_upper(privs, 1) LOOP - BEGIN - IF pg_catalog.has_database_privilege($1, $2, privs[i]) THEN - grants := grants || privs[i]; - END IF; - EXCEPTION WHEN invalid_catalog_name THEN - -- Not a valid db name. - RETURN '{invalid_catalog_name}'; - WHEN undefined_object THEN - -- Not a valid role. - RETURN '{undefined_role}'; - WHEN invalid_parameter_value THEN - -- Not a valid permission on this version of PostgreSQL; ignore; - END; - END LOOP; - RETURN grants; -END; -$$ LANGUAGE plpgsql; - --- database_privs_are ( db, user, privileges[], description ) -CREATE OR REPLACE FUNCTION database_privs_are ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - grants TEXT[] := _get_db_privs( $2, $1::TEXT ); -BEGIN - IF grants[1] = 'invalid_catalog_name' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Database ' || quote_ident($1) || ' does not exist' - ); - ELSIF grants[1] = 'undefined_role' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Role ' || quote_ident($2) || ' does not exist' - ); - END IF; - RETURN _assets_are('privileges', grants, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- database_privs_are ( db, user, privileges[] ) -CREATE OR REPLACE FUNCTION database_privs_are ( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT database_privs_are( - $1, $2, $3, - 'Role ' || quote_ident($2) || ' should be granted ' - || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END - || ' on database ' || quote_ident($1) - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_func_privs(TEXT, TEXT) -RETURNS TEXT[] AS $$ -BEGIN - IF pg_catalog.has_function_privilege($1, $2, 'EXECUTE') THEN - RETURN '{EXECUTE}'; - ELSE - RETURN '{}'; - END IF; -EXCEPTION - -- Not a valid func name. - WHEN undefined_function THEN RETURN '{undefined_function}'; - -- Not a valid role. - WHEN undefined_object THEN RETURN '{undefined_role}'; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION _fprivs_are ( TEXT, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - grants TEXT[] := _get_func_privs($2, $1); -BEGIN - IF grants[1] = 'undefined_function' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Function ' || $1 || ' does not exist' - ); - ELSIF grants[1] = 'undefined_role' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Role ' || quote_ident($2) || ' does not exist' - ); - END IF; - RETURN _assets_are('privileges', grants, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- function_privs_are ( schema, function, args[], user, privileges[], description ) -CREATE OR REPLACE FUNCTION function_privs_are ( NAME, NAME, NAME[], NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _fprivs_are( - quote_ident($1) || '.' || quote_ident($2) || '(' || array_to_string($3, ', ') || ')', - $4, $5, $6 - ); -$$ LANGUAGE SQL; - --- function_privs_are ( schema, function, args[], user, privileges[] ) -CREATE OR REPLACE FUNCTION function_privs_are ( NAME, NAME, NAME[], NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT function_privs_are( - $1, $2, $3, $4, $5, - 'Role ' || quote_ident($4) || ' should be granted ' - || CASE WHEN $5[1] IS NULL THEN 'no privileges' ELSE array_to_string($5, ', ') END - || ' on function ' || quote_ident($1) || '.' || quote_ident($2) - || '(' || array_to_string($3, ', ') || ')' - ); -$$ LANGUAGE SQL; - --- function_privs_are ( function, args[], user, privileges[], description ) -CREATE OR REPLACE FUNCTION function_privs_are ( NAME, NAME[], NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _fprivs_are( - quote_ident($1) || '(' || array_to_string($2, ', ') || ')', - $3, $4, $5 - ); -$$ LANGUAGE SQL; - --- function_privs_are ( function, args[], user, privileges[] ) -CREATE OR REPLACE FUNCTION function_privs_are ( NAME, NAME[], NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT function_privs_are( - $1, $2, $3, $4, - 'Role ' || quote_ident($3) || ' should be granted ' - || CASE WHEN $4[1] IS NULL THEN 'no privileges' ELSE array_to_string($4, ', ') END - || ' on function ' || quote_ident($1) || '(' || array_to_string($2, ', ') || ')' - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_lang_privs (NAME, TEXT) -RETURNS TEXT[] AS $$ -BEGIN - IF pg_catalog.has_language_privilege($1, $2, 'USAGE') THEN - RETURN '{USAGE}'; - ELSE - RETURN '{}'; - END IF; -EXCEPTION WHEN undefined_object THEN - -- Same error code for unknown user or language. So figure out which. - RETURN CASE WHEN SQLERRM LIKE '%' || $1 || '%' THEN - '{undefined_role}' - ELSE - '{undefined_language}' - END; -END; -$$ LANGUAGE plpgsql; - --- language_privs_are ( lang, user, privileges[], description ) -CREATE OR REPLACE FUNCTION language_privs_are ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - grants TEXT[] := _get_lang_privs( $2, quote_ident($1) ); -BEGIN - IF grants[1] = 'undefined_language' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Language ' || quote_ident($1) || ' does not exist' - ); - ELSIF grants[1] = 'undefined_role' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Role ' || quote_ident($2) || ' does not exist' - ); - END IF; - RETURN _assets_are('privileges', grants, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- language_privs_are ( lang, user, privileges[] ) -CREATE OR REPLACE FUNCTION language_privs_are ( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT language_privs_are( - $1, $2, $3, - 'Role ' || quote_ident($2) || ' should be granted ' - || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END - || ' on language ' || quote_ident($1) - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_schema_privs(NAME, TEXT) -RETURNS TEXT[] AS $$ -DECLARE - privs TEXT[] := ARRAY['CREATE', 'USAGE']; - grants TEXT[] := '{}'; -BEGIN - FOR i IN 1..array_upper(privs, 1) LOOP - IF pg_catalog.has_schema_privilege($1, $2, privs[i]) THEN - grants := grants || privs[i]; - END IF; - END LOOP; - RETURN grants; -EXCEPTION - -- Not a valid schema name. - WHEN invalid_schema_name THEN RETURN '{invalid_schema_name}'; - -- Not a valid role. - WHEN undefined_object THEN RETURN '{undefined_role}'; -END; -$$ LANGUAGE plpgsql; - --- schema_privs_are ( schema, user, privileges[], description ) -CREATE OR REPLACE FUNCTION schema_privs_are ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - grants TEXT[] := _get_schema_privs( $2, $1::TEXT ); -BEGIN - IF grants[1] = 'invalid_schema_name' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Schema ' || quote_ident($1) || ' does not exist' - ); - ELSIF grants[1] = 'undefined_role' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Role ' || quote_ident($2) || ' does not exist' - ); - END IF; - RETURN _assets_are('privileges', grants, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- schema_privs_are ( schema, user, privileges[] ) -CREATE OR REPLACE FUNCTION schema_privs_are ( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT schema_privs_are( - $1, $2, $3, - 'Role ' || quote_ident($2) || ' should be granted ' - || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END - || ' on schema ' || quote_ident($1) - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_tablespaceprivs (NAME, TEXT) -RETURNS TEXT[] AS $$ -BEGIN - IF pg_catalog.has_tablespace_privilege($1, $2, 'CREATE') THEN - RETURN '{CREATE}'; - ELSE - RETURN '{}'; - END IF; -EXCEPTION WHEN undefined_object THEN - -- Same error code for unknown user or tablespace. So figure out which. - RETURN CASE WHEN SQLERRM LIKE '%' || $1 || '%' THEN - '{undefined_role}' - ELSE - '{undefined_tablespace}' - END; -END; -$$ LANGUAGE plpgsql; - --- tablespace_privs_are ( tablespace, user, privileges[], description ) -CREATE OR REPLACE FUNCTION tablespace_privs_are ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - grants TEXT[] := _get_tablespaceprivs( $2, $1::TEXT ); -BEGIN - IF grants[1] = 'undefined_tablespace' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Tablespace ' || quote_ident($1) || ' does not exist' - ); - ELSIF grants[1] = 'undefined_role' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Role ' || quote_ident($2) || ' does not exist' - ); - END IF; - RETURN _assets_are('privileges', grants, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- tablespace_privs_are ( tablespace, user, privileges[] ) -CREATE OR REPLACE FUNCTION tablespace_privs_are ( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT tablespace_privs_are( - $1, $2, $3, - 'Role ' || quote_ident($2) || ' should be granted ' - || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END - || ' on tablespace ' || quote_ident($1) - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_sequence_privs(NAME, TEXT) -RETURNS TEXT[] AS $$ -DECLARE - privs TEXT[] := ARRAY['SELECT', 'UPDATE', 'USAGE']; - grants TEXT[] := '{}'; -BEGIN - FOR i IN 1..array_upper(privs, 1) LOOP - BEGIN - IF pg_catalog.has_sequence_privilege($1, $2, privs[i]) THEN - grants := grants || privs[i]; - END IF; - EXCEPTION WHEN undefined_table THEN - -- Not a valid sequence name. - RETURN '{undefined_table}'; - WHEN undefined_object THEN - -- Not a valid role. - RETURN '{undefined_role}'; - WHEN invalid_parameter_value THEN - -- Not a valid permission on this version of PostgreSQL; ignore; - END; - END LOOP; - RETURN grants; -END; -$$ LANGUAGE plpgsql; - --- sequence_privs_are ( schema, sequence, user, privileges[], description ) -CREATE OR REPLACE FUNCTION sequence_privs_are ( NAME, NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - grants TEXT[] := _get_sequence_privs( $3, quote_ident($1) || '.' || quote_ident($2) ); -BEGIN - IF grants[1] = 'undefined_table' THEN - RETURN ok(FALSE, $5) || E'\n' || diag( - ' Sequence ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' - ); - ELSIF grants[1] = 'undefined_role' THEN - RETURN ok(FALSE, $5) || E'\n' || diag( - ' Role ' || quote_ident($3) || ' does not exist' - ); - END IF; - RETURN _assets_are('privileges', grants, $4, $5); -END; -$$ LANGUAGE plpgsql; - --- sequence_privs_are ( schema, sequence, user, privileges[] ) -CREATE OR REPLACE FUNCTION sequence_privs_are ( NAME, NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT sequence_privs_are( - $1, $2, $3, $4, - 'Role ' || quote_ident($3) || ' should be granted ' - || CASE WHEN $4[1] IS NULL THEN 'no privileges' ELSE array_to_string($4, ', ') END - || ' on sequence '|| quote_ident($1) || '.' || quote_ident($2) - ); -$$ LANGUAGE SQL; - --- sequence_privs_are ( sequence, user, privileges[], description ) -CREATE OR REPLACE FUNCTION sequence_privs_are ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - grants TEXT[] := _get_sequence_privs( $2, quote_ident($1) ); -BEGIN - IF grants[1] = 'undefined_table' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Sequence ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' - ); - ELSIF grants[1] = 'undefined_role' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Role ' || quote_ident($2) || ' does not exist' - ); - END IF; - RETURN _assets_are('privileges', grants, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- sequence_privs_are ( sequence, user, privileges[] ) -CREATE OR REPLACE FUNCTION sequence_privs_are ( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT sequence_privs_are( - $1, $2, $3, - 'Role ' || quote_ident($2) || ' should be granted ' - || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END - || ' on sequence ' || quote_ident($1) - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_ac_privs(NAME, TEXT) -RETURNS TEXT[] AS $$ -DECLARE - privs TEXT[] := ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE']; - grants TEXT[] := '{}'; -BEGIN - FOR i IN 1..array_upper(privs, 1) LOOP - BEGIN - IF pg_catalog.has_any_column_privilege($1, $2, privs[i]) THEN - grants := grants || privs[i]; - END IF; - EXCEPTION WHEN undefined_table THEN - -- Not a valid table name. - RETURN '{undefined_table}'; - WHEN undefined_object THEN - -- Not a valid role. - RETURN '{undefined_role}'; - WHEN invalid_parameter_value THEN - -- Not a valid permission on this version of PostgreSQL; ignore; - END; - END LOOP; - RETURN grants; -END; -$$ LANGUAGE plpgsql; - --- any_column_privs_are ( schema, table, user, privileges[], description ) -CREATE OR REPLACE FUNCTION any_column_privs_are ( NAME, NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - grants TEXT[] := _get_ac_privs( $3, quote_ident($1) || '.' || quote_ident($2) ); -BEGIN - IF grants[1] = 'undefined_table' THEN - RETURN ok(FALSE, $5) || E'\n' || diag( - ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' - ); - ELSIF grants[1] = 'undefined_role' THEN - RETURN ok(FALSE, $5) || E'\n' || diag( - ' Role ' || quote_ident($3) || ' does not exist' - ); - END IF; - RETURN _assets_are('privileges', grants, $4, $5); -END; -$$ LANGUAGE plpgsql; - --- any_column_privs_are ( schema, table, user, privileges[] ) -CREATE OR REPLACE FUNCTION any_column_privs_are ( NAME, NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT any_column_privs_are( - $1, $2, $3, $4, - 'Role ' || quote_ident($3) || ' should be granted ' - || CASE WHEN $4[1] IS NULL THEN 'no privileges' ELSE array_to_string($4, ', ') END - || ' on any column in '|| quote_ident($1) || '.' || quote_ident($2) - ); -$$ LANGUAGE SQL; - --- any_column_privs_are ( table, user, privileges[], description ) -CREATE OR REPLACE FUNCTION any_column_privs_are ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - grants TEXT[] := _get_ac_privs( $2, quote_ident($1) ); -BEGIN - IF grants[1] = 'undefined_table' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' - ); - ELSIF grants[1] = 'undefined_role' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Role ' || quote_ident($2) || ' does not exist' - ); - END IF; - RETURN _assets_are('privileges', grants, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- any_column_privs_are ( table, user, privileges[] ) -CREATE OR REPLACE FUNCTION any_column_privs_are ( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT any_column_privs_are( - $1, $2, $3, - 'Role ' || quote_ident($2) || ' should be granted ' - || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END - || ' on any column in ' || quote_ident($1) - ); -$$ LANGUAGE SQL; - --- _get_col_privs(user, table, column) -CREATE OR REPLACE FUNCTION _get_col_privs(NAME, TEXT, NAME) -RETURNS TEXT[] AS $$ -DECLARE - privs TEXT[] := ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE']; - grants TEXT[] := '{}'; -BEGIN - FOR i IN 1..array_upper(privs, 1) LOOP - IF pg_catalog.has_column_privilege($1, $2, $3, privs[i]) THEN - grants := grants || privs[i]; - END IF; - END LOOP; - RETURN grants; -EXCEPTION - -- Not a valid column name. - WHEN undefined_column THEN RETURN '{undefined_column}'; - -- Not a valid table name. - WHEN undefined_table THEN RETURN '{undefined_table}'; - -- Not a valid role. - WHEN undefined_object THEN RETURN '{undefined_role}'; -END; -$$ LANGUAGE plpgsql; - --- column_privs_are ( schema, table, column, user, privileges[], description ) -CREATE OR REPLACE FUNCTION column_privs_are ( NAME, NAME, NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - grants TEXT[] := _get_col_privs( $4, quote_ident($1) || '.' || quote_ident($2), $3 ); -BEGIN - IF grants[1] = 'undefined_column' THEN - RETURN ok(FALSE, $6) || E'\n' || diag( - ' Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) - || ' does not exist' - ); - ELSIF grants[1] = 'undefined_table' THEN - RETURN ok(FALSE, $6) || E'\n' || diag( - ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' - ); - ELSIF grants[1] = 'undefined_role' THEN - RETURN ok(FALSE, $6) || E'\n' || diag( - ' Role ' || quote_ident($4) || ' does not exist' - ); - END IF; - RETURN _assets_are('privileges', grants, $5, $6); -END; -$$ LANGUAGE plpgsql; - --- column_privs_are ( schema, table, column, user, privileges[] ) -CREATE OR REPLACE FUNCTION column_privs_are ( NAME, NAME, NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT column_privs_are( - $1, $2, $3, $4, $5, - 'Role ' || quote_ident($4) || ' should be granted ' - || CASE WHEN $5[1] IS NULL THEN 'no privileges' ELSE array_to_string($5, ', ') END - || ' on column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) - ); -$$ LANGUAGE SQL; - --- column_privs_are ( table, column, user, privileges[], description ) -CREATE OR REPLACE FUNCTION column_privs_are ( NAME, NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - grants TEXT[] := _get_col_privs( $3, quote_ident($1), $2 ); -BEGIN - IF grants[1] = 'undefined_column' THEN - RETURN ok(FALSE, $5) || E'\n' || diag( - ' Column ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' - ); - ELSIF grants[1] = 'undefined_table' THEN - RETURN ok(FALSE, $5) || E'\n' || diag( - ' Table ' || quote_ident($1) || ' does not exist' - ); - ELSIF grants[1] = 'undefined_role' THEN - RETURN ok(FALSE, $5) || E'\n' || diag( - ' Role ' || quote_ident($3) || ' does not exist' - ); - END IF; - RETURN _assets_are('privileges', grants, $4, $5); -END; -$$ LANGUAGE plpgsql; - --- column_privs_are ( table, column, user, privileges[] ) -CREATE OR REPLACE FUNCTION column_privs_are ( NAME, NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT column_privs_are( - $1, $2, $3, $4, - 'Role ' || quote_ident($3) || ' should be granted ' - || CASE WHEN $4[1] IS NULL THEN 'no privileges' ELSE array_to_string($4, ', ') END - || ' on column ' || quote_ident($1) || '.' || quote_ident($2) - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_fdw_privs (NAME, TEXT) -RETURNS TEXT[] AS $$ -BEGIN - IF pg_catalog.has_foreign_data_wrapper_privilege($1, $2, 'USAGE') THEN - RETURN '{USAGE}'; - ELSE - RETURN '{}'; - END IF; -EXCEPTION WHEN undefined_object THEN - -- Same error code for unknown user or fdw. So figure out which. - RETURN CASE WHEN SQLERRM LIKE '%' || $1 || '%' THEN - '{undefined_role}' - ELSE - '{undefined_fdw}' - END; -END; -$$ LANGUAGE plpgsql; - --- fdw_privs_are ( fdw, user, privileges[], description ) -CREATE OR REPLACE FUNCTION fdw_privs_are ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - grants TEXT[] := _get_fdw_privs( $2, $1::TEXT ); -BEGIN - IF grants[1] = 'undefined_fdw' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' FDW ' || quote_ident($1) || ' does not exist' - ); - ELSIF grants[1] = 'undefined_role' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Role ' || quote_ident($2) || ' does not exist' - ); - END IF; - RETURN _assets_are('privileges', grants, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- fdw_privs_are ( fdw, user, privileges[] ) -CREATE OR REPLACE FUNCTION fdw_privs_are ( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT fdw_privs_are( - $1, $2, $3, - 'Role ' || quote_ident($2) || ' should be granted ' - || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END - || ' on FDW ' || quote_ident($1) - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_server_privs (NAME, TEXT) -RETURNS TEXT[] AS $$ -BEGIN - IF pg_catalog.has_server_privilege($1, $2, 'USAGE') THEN - RETURN '{USAGE}'; - ELSE - RETURN '{}'; - END IF; -EXCEPTION WHEN undefined_object THEN - -- Same error code for unknown user or server. So figure out which. - RETURN CASE WHEN SQLERRM LIKE '%' || $1 || '%' THEN - '{undefined_role}' - ELSE - '{undefined_server}' - END; -END; -$$ LANGUAGE plpgsql; - --- server_privs_are ( server, user, privileges[], description ) -CREATE OR REPLACE FUNCTION server_privs_are ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - grants TEXT[] := _get_server_privs( $2, $1::TEXT ); -BEGIN - IF grants[1] = 'undefined_server' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Server ' || quote_ident($1) || ' does not exist' - ); - ELSIF grants[1] = 'undefined_role' THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - ' Role ' || quote_ident($2) || ' does not exist' - ); - END IF; - RETURN _assets_are('privileges', grants, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- server_privs_are ( server, user, privileges[] ) -CREATE OR REPLACE FUNCTION server_privs_are ( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT server_privs_are( - $1, $2, $3, - 'Role ' || quote_ident($2) || ' should be granted ' - || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END - || ' on server ' || quote_ident($1) - ); -$$ LANGUAGE SQL; - --- materialized_views_are( schema, materialized_views, description ) -CREATE OR REPLACE FUNCTION materialized_views_are ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( 'Materialized views', _extras('m', $1, $2), _missing('m', $1, $2), $3); -$$ LANGUAGE SQL; - --- materialized_views_are( materialized_views, description ) -CREATE OR REPLACE FUNCTION materialized_views_are ( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( 'Materialized views', _extras('m', $1), _missing('m', $1), $2); -$$ LANGUAGE SQL; - --- materialized_views_are( schema, materialized_views ) -CREATE OR REPLACE FUNCTION materialized_views_are ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _are( - 'Materialized views', _extras('m', $1, $2), _missing('m', $1, $2), - 'Schema ' || quote_ident($1) || ' should have the correct materialized views' - ); -$$ LANGUAGE SQL; - --- materialized_views_are( materialized_views ) -CREATE OR REPLACE FUNCTION materialized_views_are ( NAME[] ) -RETURNS TEXT AS $$ - SELECT _are( - 'Materialized views', _extras('m', $1), _missing('m', $1), - 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct materialized views' - ); -$$ LANGUAGE SQL; - --- materialized_view_owner_is ( schema, materialized_view, user, description ) -CREATE OR REPLACE FUNCTION materialized_view_owner_is ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_rel_owner('m'::char, $1, $2); -BEGIN - -- Make sure the materialized view exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $4) || E'\n' || diag( - E' Materialized view ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' - ); - END IF; - - RETURN is(owner, $3, $4); -END; -$$ LANGUAGE plpgsql; - --- materialized_view_owner_is ( schema, materialized_view, user ) -CREATE OR REPLACE FUNCTION materialized_view_owner_is ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT materialized_view_owner_is( - $1, $2, $3, - 'Materialized view ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) - ); -$$ LANGUAGE sql; - --- materialized_view_owner_is ( materialized_view, user, description ) -CREATE OR REPLACE FUNCTION materialized_view_owner_is ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -DECLARE - owner NAME := _get_rel_owner('m'::char, $1); -BEGIN - -- Make sure the materialized view exists. - IF owner IS NULL THEN - RETURN ok(FALSE, $3) || E'\n' || diag( - E' Materialized view ' || quote_ident($1) || ' does not exist' - ); - END IF; - - RETURN is(owner, $2, $3); -END; -$$ LANGUAGE plpgsql; - --- materialized_view_owner_is ( materialized_view, user ) -CREATE OR REPLACE FUNCTION materialized_view_owner_is ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT materialized_view_owner_is( - $1, $2, - 'Materialized view ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) - ); -$$ LANGUAGE sql; - --- has_materialized_view( schema, materialized_view, description ) -CREATE OR REPLACE FUNCTION has_materialized_view ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists( 'm', $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- has_materialized_view( materialized_view, description ) -CREATE OR REPLACE FUNCTION has_materialized_view ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists( 'm', $1 ), $2 ); -$$ LANGUAGE SQL; - --- has_materialized_view( materialized_view ) -CREATE OR REPLACE FUNCTION has_materialized_view ( NAME ) -RETURNS TEXT AS $$ - SELECT has_materialized_view( $1, 'Materialized view ' || quote_ident($1) || ' should exist' ); -$$ LANGUAGE SQL; - --- hasnt_materialized_view( schema, materialized_view, description ) -CREATE OR REPLACE FUNCTION hasnt_materialized_view ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _rexists( 'm', $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- hasnt_materialized_view( materialized_view, description ) -CREATE OR REPLACE FUNCTION hasnt_materialized_view ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _rexists( 'm', $1 ), $2 ); -$$ LANGUAGE SQL; - --- hasnt_materialized_view( materialized_view ) -CREATE OR REPLACE FUNCTION hasnt_materialized_view ( NAME ) -RETURNS TEXT AS $$ - SELECT hasnt_materialized_view( $1, 'Materialized view ' || quote_ident($1) || ' should not exist' ); -$$ LANGUAGE SQL; - - --- foreign_tables_are( schema, tables, description ) -CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( 'foreign tables', _extras('f', $1, $2), _missing('f', $1, $2), $3); -$$ LANGUAGE SQL; - --- foreign_tables_are( tables, description ) -CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( 'foreign tables', _extras('f', $1), _missing('f', $1), $2); -$$ LANGUAGE SQL; - --- foreign_tables_are( schema, tables ) -CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _are( - 'foreign tables', _extras('f', $1, $2), _missing('f', $1, $2), - 'Schema ' || quote_ident($1) || ' should have the correct foreign tables' - ); -$$ LANGUAGE SQL; - --- foreign_tables_are( tables ) -CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME[] ) -RETURNS TEXT AS $$ - SELECT _are( - 'foreign tables', _extras('f', $1), _missing('f', $1), - 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct foreign tables' - ); -$$ LANGUAGE SQL; - -GRANT SELECT ON tap_funky TO PUBLIC; -GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; - --- Get extensions in a given schema -CREATE OR REPLACE FUNCTION _extensions( NAME ) -RETURNS SETOF NAME AS $$ - SELECT e.extname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_extension e ON n.oid = e.extnamespace - WHERE n.nspname = $1 -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _extensions() -RETURNS SETOF NAME AS $$ - SELECT extname FROM pg_catalog.pg_extension -$$ LANGUAGE SQL; - --- extensions_are( schema, extensions, description ) -CREATE OR REPLACE FUNCTION extensions_are( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'extensions', - ARRAY(SELECT _extensions($1) EXCEPT SELECT unnest($2)), - ARRAY(SELECT unnest($2) EXCEPT SELECT _extensions($1)), - $3 - ); -$$ LANGUAGE SQL; - --- extensions_are( schema, extensions) -CREATE OR REPLACE FUNCTION extensions_are( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT extensions_are( - $1, $2, - 'Schema ' || quote_ident($1) || ' should have the correct extensions' - ); -$$ LANGUAGE SQL; - --- extensions_are( extensions, description ) -CREATE OR REPLACE FUNCTION extensions_are( NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'extensions', - ARRAY(SELECT _extensions() EXCEPT SELECT unnest($1)), - ARRAY(SELECT unnest($1) EXCEPT SELECT _extensions()), - $2 - ); -$$ LANGUAGE SQL; - --- extensions_are( schema, extensions) -CREATE OR REPLACE FUNCTION extensions_are( NAME[] ) -RETURNS TEXT AS $$ - SELECT extensions_are($1, 'Should have the correct extensions'); -$$ LANGUAGE SQL; - --- check extension exists function with schema name -CREATE OR REPLACE FUNCTION _ext_exists( NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS ( - SELECT TRUE - FROM pg_catalog.pg_extension ex - JOIN pg_catalog.pg_namespace n ON ex.extnamespace = n.oid - WHERE n.nspname = $1 - AND ex.extname = $2 - ); -$$ LANGUAGE SQL; - --- check extension exists function without schema name -CREATE OR REPLACE FUNCTION _ext_exists( NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS ( - SELECT TRUE - FROM pg_catalog.pg_extension ex - WHERE ex.extname = $1 - ); -$$ LANGUAGE SQL; - --- has_extension( schema, name, description ) -CREATE OR REPLACE FUNCTION has_extension( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _ext_exists( $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- has_extension( schema, name ) -CREATE OR REPLACE FUNCTION has_extension( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _ext_exists( $1, $2 ), - 'Extension ' || quote_ident($2) - || ' should exist in schema ' || quote_ident($1) ); -$$ LANGUAGE SQL; - --- has_extension( name, description ) -CREATE OR REPLACE FUNCTION has_extension( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _ext_exists( $1 ), $2) -$$ LANGUAGE SQL; - --- has_extension( name ) -CREATE OR REPLACE FUNCTION has_extension( NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _ext_exists( $1 ), - 'Extension ' || quote_ident($1) || ' should exist' ); -$$ LANGUAGE SQL; - --- hasnt_extension( schema, name, description ) -CREATE OR REPLACE FUNCTION hasnt_extension( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _ext_exists( $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- hasnt_extension( schema, name ) -CREATE OR REPLACE FUNCTION hasnt_extension( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _ext_exists( $1, $2 ), - 'Extension ' || quote_ident($2) - || ' should not exist in schema ' || quote_ident($1) ); -$$ LANGUAGE SQL; - --- hasnt_extension( name, description ) -CREATE OR REPLACE FUNCTION hasnt_extension( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _ext_exists( $1 ), $2) -$$ LANGUAGE SQL; - --- hasnt_extension( name ) -CREATE OR REPLACE FUNCTION hasnt_extension( NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _ext_exists( $1 ), - 'Extension ' || quote_ident($1) || ' should not exist' ); -$$ LANGUAGE SQL; - --- is_partitioned( schema, table, description ) -CREATE OR REPLACE FUNCTION is_partitioned ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists('p', $1, $2), $3); -$$ LANGUAGE sql; - --- is_partitioned( schema, table ) -CREATE OR REPLACE FUNCTION is_partitioned ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _rexists('p', $1, $2), - 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should be partitioned' - ); -$$ LANGUAGE sql; - --- is_partitioned( table, description ) -CREATE OR REPLACE FUNCTION is_partitioned ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists('p', $1), $2); -$$ LANGUAGE sql; - --- is_partitioned( table ) -CREATE OR REPLACE FUNCTION is_partitioned ( NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _rexists('p', $1), - 'Table ' || quote_ident($1) || ' should be partitioned' - ); -$$ LANGUAGE sql; - --- isnt_partitioned( schema, table, description ) -CREATE OR REPLACE FUNCTION isnt_partitioned ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _rexists('p', $1, $2), $3); -$$ LANGUAGE sql; - --- isnt_partitioned( schema, table ) -CREATE OR REPLACE FUNCTION isnt_partitioned ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _rexists('p', $1, $2), - 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should not be partitioned' - ); -$$ LANGUAGE sql; - --- isnt_partitioned( table, description ) -CREATE OR REPLACE FUNCTION isnt_partitioned ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _rexists('p', $1), $2); -$$ LANGUAGE sql; - --- isnt_partitioned( table ) -CREATE OR REPLACE FUNCTION isnt_partitioned ( NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _rexists('p', $1), - 'Table ' || quote_ident($1) || ' should not be partitioned' - ); -$$ LANGUAGE sql; - --- _partof( child_schema, child_table, parent_schema, parent_table ) -CREATE OR REPLACE FUNCTION _partof ( NAME, NAME, NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_namespace cn - JOIN pg_catalog.pg_class cc ON cn.oid = cc.relnamespace - JOIN pg_catalog.pg_inherits i ON cc.oid = i.inhrelid - JOIN pg_catalog.pg_class pc ON i.inhparent = pc.oid - JOIN pg_catalog.pg_namespace pn ON pc.relnamespace = pn.oid - WHERE cn.nspname = $1 - AND cc.relname = $2 - AND cc.relispartition - AND pn.nspname = $3 - AND pc.relname = $4 - AND pc.relkind = 'p' - ) -$$ LANGUAGE sql; - --- _partof( child_table, parent_table ) -CREATE OR REPLACE FUNCTION _partof ( NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_class cc - JOIN pg_catalog.pg_inherits i ON cc.oid = i.inhrelid - JOIN pg_catalog.pg_class pc ON i.inhparent = pc.oid - WHERE cc.relname = $1 - AND cc.relispartition - AND pc.relname = $2 - AND pc.relkind = 'p' - AND pg_catalog.pg_table_is_visible(cc.oid) - AND pg_catalog.pg_table_is_visible(pc.oid) - ) -$$ LANGUAGE sql; - --- is_partition_of( child_schema, child_table, parent_schema, parent_table, description ) -CREATE OR REPLACE FUNCTION is_partition_of ( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _partof($1, $2, $3, $4), $5); -$$ LANGUAGE sql; - --- is_partition_of( child_schema, child_table, parent_schema, parent_table ) -CREATE OR REPLACE FUNCTION is_partition_of ( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _partof($1, $2, $3, $4), - 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should be a partition of ' - || quote_ident($3) || '.' || quote_ident($4) - ); -$$ LANGUAGE sql; - --- is_partition_of( child_table, parent_table, description ) -CREATE OR REPLACE FUNCTION is_partition_of ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _partof($1, $2), $3); -$$ LANGUAGE sql; - --- is_partition_of( child_table, parent_table ) -CREATE OR REPLACE FUNCTION is_partition_of ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _partof($1, $2), - 'Table ' || quote_ident($1) || ' should be a partition of ' || quote_ident($2) - ); -$$ LANGUAGE sql; - --- _parts(schema, table) -CREATE OR REPLACE FUNCTION _parts( NAME, NAME ) -RETURNS SETOF NAME AS $$ - SELECT i.inhrelid::regclass::name - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - JOIN pg_catalog.pg_inherits i ON c.oid = i.inhparent - WHERE n.nspname = $1 - AND c.relname = $2 - AND c.relkind = 'p' -$$ LANGUAGE SQL; - --- _parts(table) -CREATE OR REPLACE FUNCTION _parts( NAME ) -RETURNS SETOF NAME AS $$ - SELECT i.inhrelid::regclass::name - FROM pg_catalog.pg_class c - JOIN pg_catalog.pg_inherits i ON c.oid = i.inhparent - WHERE c.relname = $1 - AND c.relkind = 'p' - AND pg_catalog.pg_table_is_visible(c.oid) -$$ LANGUAGE SQL; - --- partitions_are( schema, table, partitions, description ) -CREATE OR REPLACE FUNCTION partitions_are( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'partitions', - ARRAY(SELECT _parts($1, $2) EXCEPT SELECT unnest($3)), - ARRAY(SELECT unnest($3) EXCEPT SELECT _parts($1, $2)), - $4 - ); -$$ LANGUAGE SQL; - --- partitions_are( schema, table, partitions ) -CREATE OR REPLACE FUNCTION partitions_are( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT partitions_are( - $1, $2, $3, - 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct partitions' - ); -$$ LANGUAGE SQL; - --- partitions_are( table, partitions, description ) -CREATE OR REPLACE FUNCTION partitions_are( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'partitions', - ARRAY(SELECT _parts($1) EXCEPT SELECT unnest($2)), - ARRAY(SELECT unnest($2) EXCEPT SELECT _parts($1)), - $3 - ); -$$ LANGUAGE SQL; - --- partitions_are( table, partitions ) -CREATE OR REPLACE FUNCTION partitions_are( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT partitions_are( - $1, $2, - 'Table ' || quote_ident($1) || ' should have the correct partitions' - ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _ident_array_to_sorted_string( name[], text ) -RETURNS text AS $$ - SELECT array_to_string(ARRAY( - SELECT quote_ident($1[i]) - FROM generate_series(1, array_upper($1, 1)) s(i) - ORDER BY $1[i] - ), $2); -$$ LANGUAGE SQL immutable; - -CREATE OR REPLACE FUNCTION _array_to_sorted_string( name[], text ) -RETURNS text AS $$ - SELECT array_to_string(ARRAY( - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - ORDER BY $1[i] - ), $2); -$$ LANGUAGE SQL immutable; - --- policies_are( schema, table, policies[], description ) -CREATE OR REPLACE FUNCTION policies_are( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'policies', - ARRAY( - SELECT p.polname - FROM pg_catalog.pg_policy p - JOIN pg_catalog.pg_class c ON c.oid = p.polrelid - JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE n.nspname = $1 - AND c.relname = $2 - EXCEPT - SELECT $3[i] - FROM generate_series(1, array_upper($3, 1)) s(i) - ), - ARRAY( - SELECT $3[i] - FROM generate_series(1, array_upper($3, 1)) s(i) - EXCEPT - SELECT p.polname - FROM pg_catalog.pg_policy p - JOIN pg_catalog.pg_class c ON c.oid = p.polrelid - JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE n.nspname = $1 - AND c.relname = $2 - ), - $4 - ); -$$ LANGUAGE SQL; - --- policies_are( schema, table, policies[] ) -CREATE OR REPLACE FUNCTION policies_are( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT policies_are( $1, $2, $3, 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct policies' ); -$$ LANGUAGE SQL; - --- policies_are( table, policies[], description ) -CREATE OR REPLACE FUNCTION policies_are( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'policies', - ARRAY( - SELECT p.polname - FROM pg_catalog.pg_policy p - JOIN pg_catalog.pg_class c ON c.oid = p.polrelid - JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE c.relname = $1 - AND n.nspname NOT IN ('pg_catalog', 'information_schema') - EXCEPT - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - ), - ARRAY( - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - EXCEPT - SELECT p.polname - FROM pg_catalog.pg_policy p - JOIN pg_catalog.pg_class c ON c.oid = p.polrelid - JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - AND n.nspname NOT IN ('pg_catalog', 'information_schema') - ), - $3 - ); -$$ LANGUAGE SQL; - --- policies_are( table, policies[] ) -CREATE OR REPLACE FUNCTION policies_are( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT policies_are( $1, $2, 'Table ' || quote_ident($1) || ' should have the correct policies' ); -$$ LANGUAGE SQL; - --- policy_roles_are( schema, table, policy, roles[], description ) -CREATE OR REPLACE FUNCTION policy_roles_are( NAME, NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'policy roles', - ARRAY( - SELECT pr.rolname - FROM pg_catalog.pg_policy AS pp - JOIN pg_catalog.pg_roles AS pr ON pr.oid = ANY (pp.polroles) - JOIN pg_catalog.pg_class AS pc ON pc.oid = pp.polrelid - JOIN pg_catalog.pg_namespace AS pn ON pn.oid = pc.relnamespace - WHERE pn.nspname = $1 - AND pc.relname = $2 - AND pp.polname = $3 - EXCEPT - SELECT $4[i] - FROM generate_series(1, array_upper($4, 1)) s(i) - ), - ARRAY( - SELECT $4[i] - FROM generate_series(1, array_upper($4, 1)) s(i) - EXCEPT - SELECT pr.rolname - FROM pg_catalog.pg_policy AS pp - JOIN pg_catalog.pg_roles AS pr ON pr.oid = ANY (pp.polroles) - JOIN pg_catalog.pg_class AS pc ON pc.oid = pp.polrelid - JOIN pg_catalog.pg_namespace AS pn ON pn.oid = pc.relnamespace - WHERE pn.nspname = $1 - AND pc.relname = $2 - AND pp.polname = $3 - ), - $5 - ); -$$ LANGUAGE SQL; - --- policy_roles_are( schema, table, policy, roles[] ) -CREATE OR REPLACE FUNCTION policy_roles_are( NAME, NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT policy_roles_are( $1, $2, $3, $4, 'Policy ' || quote_ident($3) || ' for table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct roles' ); -$$ LANGUAGE SQL; - --- policy_roles_are( table, policy, roles[], description ) -CREATE OR REPLACE FUNCTION policy_roles_are( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( - 'policy roles', - ARRAY( - SELECT pr.rolname - FROM pg_catalog.pg_policy AS pp - JOIN pg_catalog.pg_roles AS pr ON pr.oid = ANY (pp.polroles) - JOIN pg_catalog.pg_class AS pc ON pc.oid = pp.polrelid - JOIN pg_catalog.pg_namespace AS pn ON pn.oid = pc.relnamespace - WHERE pc.relname = $1 - AND pp.polname = $2 - AND pn.nspname NOT IN ('pg_catalog', 'information_schema') - EXCEPT - SELECT $3[i] - FROM generate_series(1, array_upper($3, 1)) s(i) - ), - ARRAY( - SELECT $3[i] - FROM generate_series(1, array_upper($3, 1)) s(i) - EXCEPT - SELECT pr.rolname - FROM pg_catalog.pg_policy AS pp - JOIN pg_catalog.pg_roles AS pr ON pr.oid = ANY (pp.polroles) - JOIN pg_catalog.pg_class AS pc ON pc.oid = pp.polrelid - JOIN pg_catalog.pg_namespace AS pn ON pn.oid = pc.relnamespace - WHERE pc.relname = $1 - AND pp.polname = $2 - AND pn.nspname NOT IN ('pg_catalog', 'information_schema') - ), - $4 - ); -$$ LANGUAGE SQL; - --- policy_roles_are( table, policy, roles[] ) -CREATE OR REPLACE FUNCTION policy_roles_are( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT policy_roles_are( $1, $2, $3, 'Policy ' || quote_ident($2) || ' for table ' || quote_ident($1) || ' should have the correct roles' ); -$$ LANGUAGE SQL; - --- policy_cmd_is( schema, table, policy, command, description ) -CREATE OR REPLACE FUNCTION policy_cmd_is( NAME, NAME, NAME, text, text ) -RETURNS TEXT AS $$ -DECLARE - cmd text; -BEGIN - SELECT - CASE pp.polcmd WHEN 'r' THEN 'SELECT' - WHEN 'a' THEN 'INSERT' - WHEN 'w' THEN 'UPDATE' - WHEN 'd' THEN 'DELETE' - ELSE 'ALL' - END - FROM pg_catalog.pg_policy AS pp - JOIN pg_catalog.pg_class AS pc ON pc.oid = pp.polrelid - JOIN pg_catalog.pg_namespace AS pn ON pn.oid = pc.relnamespace - WHERE pn.nspname = $1 - AND pc.relname = $2 - AND pp.polname = $3 - INTO cmd; - - RETURN is( cmd, upper($4), $5 ); -END; -$$ LANGUAGE plpgsql; - --- policy_cmd_is( schema, table, policy, command ) -CREATE OR REPLACE FUNCTION policy_cmd_is( NAME, NAME, NAME, text ) -RETURNS TEXT AS $$ - SELECT policy_cmd_is( - $1, $2, $3, $4, - 'Policy ' || quote_ident($3) - || ' for table ' || quote_ident($1) || '.' || quote_ident($2) - || ' should apply to ' || upper($4) || ' command' - ); -$$ LANGUAGE sql; - --- policy_cmd_is( table, policy, command, description ) -CREATE OR REPLACE FUNCTION policy_cmd_is( NAME, NAME, text, text ) -RETURNS TEXT AS $$ -DECLARE - cmd text; -BEGIN - SELECT - CASE pp.polcmd WHEN 'r' THEN 'SELECT' - WHEN 'a' THEN 'INSERT' - WHEN 'w' THEN 'UPDATE' - WHEN 'd' THEN 'DELETE' - ELSE 'ALL' - END - FROM pg_catalog.pg_policy AS pp - JOIN pg_catalog.pg_class AS pc ON pc.oid = pp.polrelid - JOIN pg_catalog.pg_namespace AS pn ON pn.oid = pc.relnamespace - WHERE pc.relname = $1 - AND pp.polname = $2 - AND pn.nspname NOT IN ('pg_catalog', 'information_schema') - INTO cmd; - - RETURN is( cmd, upper($3), $4 ); -END; -$$ LANGUAGE plpgsql; - --- policy_cmd_is( table, policy, command ) -CREATE OR REPLACE FUNCTION policy_cmd_is( NAME, NAME, text ) -RETURNS TEXT AS $$ - SELECT policy_cmd_is( - $1, $2, $3, - 'Policy ' || quote_ident($2) - || ' for table ' || quote_ident($1) - || ' should apply to ' || upper($3) || ' command' - ); -$$ LANGUAGE sql; - -/******************** INHERITANCE ***********************************************/ -/* - * Internal function to test whether the specified table in the specified schema - * has an inheritance chain. Returns true or false. - */ -CREATE OR REPLACE FUNCTION _inherited( NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - WHERE c.relkind = 'r' - AND n.nspname = $1 - AND c.relname = $2 - AND c.relhassubclass = true - ); -$$ LANGUAGE SQL; - -/* - * Internal function to test whether a specific table in the search_path has an - * inheritance chain. Returns true or false. - */ -CREATE OR REPLACE FUNCTION _inherited( NAME ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_class c - WHERE c.relkind = 'r' - AND pg_catalog.pg_table_is_visible( c.oid ) - AND c.relname = $1 - AND c.relhassubclass = true - ); -$$ LANGUAGE SQL; - --- has_inherited_tables( schema, table, description ) -CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _inherited( $1, $2 ), $3); -$$ LANGUAGE SQL; - --- has_inherited_tables( schema, table ) -CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _inherited( $1, $2 ), - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || ' should have descendents' - ); -$$ LANGUAGE SQL; - --- has_inherited_tables( table, description ) -CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _inherited( $1 ), $2 ); -$$ LANGUAGE SQL; - --- has_inherited_tables( table ) -CREATE OR REPLACE FUNCTION has_inherited_tables( NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _inherited( $1 ), - 'Table ' || quote_ident( $1 ) || ' should have descendents' - ); -$$ LANGUAGE SQL; - --- hasnt_inherited_tables( schema, table, description ) -CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _inherited( $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- hasnt_inherited_tables( schema, table ) -CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _inherited( $1, $2 ), - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || ' should not have descendents' - ); -$$ LANGUAGE SQL; - --- hasnt_inherited_tables( table, description ) -CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _inherited( $1 ), $2 ); -$$ LANGUAGE SQL; - --- hasnt_inherited_tables( table ) -CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _inherited( $1 ), - 'Table ' || quote_ident( $1 ) || ' should not have descendents' - ); -$$ LANGUAGE SQL; - -/* -* Internal function to test whether the schema-qualified table is an ancestor of -* the other schema-qualified table. The integer value is the length of the -* inheritance chain: a direct ancestor has has a chain length of 1. -*/ -CREATE OR REPLACE FUNCTION _ancestor_of( NAME, NAME, NAME, NAME, INT ) -RETURNS BOOLEAN AS $$ - WITH RECURSIVE inheritance_chain AS ( - -- select the ancestor tuple - SELECT i.inhrelid AS descendent_id, 1 AS inheritance_level - FROM pg_catalog.pg_inherits i - WHERE i.inhparent = ( - SELECT c1.oid - FROM pg_catalog.pg_class c1 - JOIN pg_catalog.pg_namespace n1 - ON c1.relnamespace = n1.oid - WHERE c1.relname = $2 - AND n1.nspname = $1 - ) - UNION - -- select the descendents - SELECT i.inhrelid AS descendent_id, - p.inheritance_level + 1 AS inheritance_level - FROM pg_catalog.pg_inherits i - JOIN inheritance_chain p - ON p.descendent_id = i.inhparent - WHERE i.inhrelid = ( - SELECT c1.oid - FROM pg_catalog.pg_class c1 - JOIN pg_catalog.pg_namespace n1 - ON c1.relnamespace = n1.oid - WHERE c1.relname = $4 - AND n1.nspname = $3 - ) - ) - SELECT EXISTS( - SELECT true - FROM inheritance_chain - WHERE inheritance_level = COALESCE($5, inheritance_level) - AND descendent_id = ( - SELECT c1.oid - FROM pg_catalog.pg_class c1 - JOIN pg_catalog.pg_namespace n1 - ON c1.relnamespace = n1.oid - WHERE c1.relname = $4 - AND n1.nspname = $3 - ) - ); -$$ LANGUAGE SQL; - -/* - * Internal function to check if not-qualified tables - * within the search_path are connected by an inheritance chain. - */ -CREATE OR REPLACE FUNCTION _ancestor_of( NAME, NAME, INT ) -RETURNS BOOLEAN AS $$ - WITH RECURSIVE inheritance_chain AS ( - -- select the ancestor tuple - SELECT i.inhrelid AS descendent_id, 1 AS inheritance_level - FROM pg_catalog.pg_inherits i - WHERE i.inhparent = ( - SELECT c1.oid - FROM pg_catalog.pg_class c1 - JOIN pg_catalog.pg_namespace n1 - ON c1.relnamespace = n1.oid - WHERE c1.relname = $1 - AND pg_catalog.pg_table_is_visible( c1.oid ) - ) - UNION - -- select the descendents - SELECT i.inhrelid AS descendent_id, - p.inheritance_level + 1 AS inheritance_level - FROM pg_catalog.pg_inherits i - JOIN inheritance_chain p - ON p.descendent_id = i.inhparent - WHERE i.inhrelid = ( - SELECT c1.oid - FROM pg_catalog.pg_class c1 - JOIN pg_catalog.pg_namespace n1 - ON c1.relnamespace = n1.oid - WHERE c1.relname = $2 - AND pg_catalog.pg_table_is_visible( c1.oid ) - ) - ) - SELECT EXISTS( - SELECT true - FROM inheritance_chain - WHERE inheritance_level = COALESCE($3, inheritance_level) - AND descendent_id = ( - SELECT c1.oid - FROM pg_catalog.pg_class c1 - JOIN pg_catalog.pg_namespace n1 - ON c1.relnamespace = n1.oid - WHERE c1.relname = $2 - AND pg_catalog.pg_table_is_visible( c1.oid ) - ) - ); -$$ LANGUAGE SQL; - --- is_ancestor_of( schema, table, schema, table, depth, description ) -CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, NAME, NAME, INT, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _ancestor_of( $1, $2, $3, $4, $5 ), $6 ); -$$ LANGUAGE SQL; - --- is_ancestor_of( schema, table, schema, table, depth ) -CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, NAME, NAME, INT ) -RETURNS TEXT AS $$ - SELECT ok( - _ancestor_of( $1, $2, $3, $4, $5 ), - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' should be ancestor ' || $5 || ' for ' - || quote_ident( $3 ) || '.' || quote_ident( $4 ) - ); -$$ LANGUAGE SQL; - --- is_ancestor_of( schema, table, schema, table, description ) -CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _ancestor_of( $1, $2, $3, $4, NULL ), $5 ); -$$ LANGUAGE SQL; - --- is_ancestor_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _ancestor_of( $1, $2, $3, $4, NULL ), - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' should be an ancestor of ' - || quote_ident( $3 ) || '.' || quote_ident( $4 ) - ); -$$ LANGUAGE SQL; - --- is_ancestor_of( table, table, depth, description ) -CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, INT, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _ancestor_of( $1, $2, $3 ), $4 ); -$$ LANGUAGE SQL; - --- is_ancestor_of( table, table, depth ) -CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, INT ) -RETURNS TEXT AS $$ - SELECT ok( - _ancestor_of( $1, $2, $3 ), - 'Table ' || quote_ident( $1 ) || ' should be ancestor ' || $3 || ' of ' || quote_ident( $2) - ); -$$ LANGUAGE SQL; - --- is_ancestor_of( table, table, description ) -CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _ancestor_of( $1, $2, NULL ), $3 ); -$$ LANGUAGE SQL; - --- is_ancestor_of( table, table ) -CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _ancestor_of( $1, $2, NULL ), - 'Table ' || quote_ident( $1 ) || ' should be an ancestor of ' || quote_ident( $2) - ); -$$ LANGUAGE SQL; - --- isnt_ancestor_of( schema, table, schema, table, depth, description ) -CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, NAME, NAME, INT, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _ancestor_of( $1, $2, $3, $4, $5 ), $6 ); -$$ LANGUAGE SQL; - --- isnt_ancestor_of( schema, table, schema, table, depth ) -CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, NAME, NAME, INT ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _ancestor_of( $1, $2, $3, $4, $5 ), - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' should not be ancestor ' || $5 || ' for ' - || quote_ident( $3 ) || '.' || quote_ident( $4 ) - ); -$$ LANGUAGE SQL; - --- isnt_ancestor_of( schema, table, schema, table, description ) -CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _ancestor_of( $1, $2, $3, $4, NULL ), $5 ); -$$ LANGUAGE SQL; - --- isnt_ancestor_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _ancestor_of( $1, $2, $3, $4, NULL ), - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' should not be an ancestor of ' - || quote_ident( $3 ) || '.' || quote_ident( $4 ) - ); -$$ LANGUAGE SQL; - --- isnt_ancestor_of( table, table, depth, description ) -CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, INT, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _ancestor_of( $1, $2, $3 ), $4 ); -$$ LANGUAGE SQL; - --- isnt_ancestor_of( table, table, depth ) -CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, INT ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _ancestor_of( $1, $2, $3 ), - 'Table ' || quote_ident( $1 ) || ' should not be ancestor ' || $3 || ' of ' || quote_ident( $2) - ); -$$ LANGUAGE SQL; - --- isnt_ancestor_of( table, table, description ) -CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _ancestor_of( $1, $2, NULL ), $3 ); -$$ LANGUAGE SQL; - --- isnt_ancestor_of( table, table ) -CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _ancestor_of( $1, $2, NULL ), - 'Table ' || quote_ident( $1 ) || ' should not be an ancestor of ' || quote_ident( $2) - ); -$$ LANGUAGE SQL; - --- is_descendent_of( schema, table, schema, table, depth, description ) -CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, NAME, NAME, INT, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _ancestor_of( $3, $4, $1, $2, $5 ), $6 ); -$$ LANGUAGE SQL; - --- is_descendent_of( schema, table, schema, table, depth ) -CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, NAME, NAME, INT ) -RETURNS TEXT AS $$ - SELECT ok( - _ancestor_of( $3, $4, $1, $2, $5 ), - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' should be descendent ' || $5 || ' from ' - || quote_ident( $3 ) || '.' || quote_ident( $4 ) - ); -$$ LANGUAGE SQL; - --- is_descendent_of( schema, table, schema, table, description ) -CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _ancestor_of( $3, $4, $1, $2, NULL ), $5 ); -$$ LANGUAGE SQL; - --- is_descendent_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _ancestor_of( $3, $4, $1, $2, NULL ), - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' should be a descendent of ' - || quote_ident( $3 ) || '.' || quote_ident( $4 ) - ); -$$ LANGUAGE SQL; - --- is_descendent_of( table, table, depth, description ) -CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, INT, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _ancestor_of( $2, $1, $3 ), $4 ); -$$ LANGUAGE SQL; - --- is_descendent_of( table, table, depth ) -CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, INT ) -RETURNS TEXT AS $$ - SELECT ok( - _ancestor_of( $2, $1, $3 ), - 'Table ' || quote_ident( $1 ) || ' should be descendent ' || $3 || ' from ' || quote_ident( $2) - ); -$$ LANGUAGE SQL; - --- is_descendent_of( table, table, description ) -CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _ancestor_of( $2, $1, NULL ), $3 ); -$$ LANGUAGE SQL; - --- is_descendent_of( table, table ) -CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _ancestor_of( $2, $1, NULL ), - 'Table ' || quote_ident( $1 ) || ' should be a descendent of ' || quote_ident( $2) - ); -$$ LANGUAGE SQL; - --- isnt_descendent_of( schema, table, schema, table, depth, description ) -CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, NAME, NAME, INT, TEXT ) -RETURNS TEXT AS $$ - SELECT ok(NOT _ancestor_of( $3, $4, $1, $2, $5 ), $6 ); -$$ LANGUAGE SQL; - --- isnt_descendent_of( schema, table, schema, table, depth ) -CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, NAME, NAME, INT ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _ancestor_of( $3, $4, $1, $2, $5 ), - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' should not be descendent ' || $5 || ' from ' - || quote_ident( $3 ) || '.' || quote_ident( $4 ) - ); -$$ LANGUAGE SQL; - --- isnt_descendent_of( schema, table, schema, table, description ) -CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok(NOT _ancestor_of( $3, $4, $1, $2, NULL ), $5 ); -$$ LANGUAGE SQL; - --- isnt_descendent_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _ancestor_of( $3, $4, $1, $2, NULL ), - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' should not be a descendent of ' - || quote_ident( $3 ) || '.' || quote_ident( $4 ) - ); -$$ LANGUAGE SQL; - --- isnt_descendent_of( table, table, depth, description ) -CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, INT, TEXT ) -RETURNS TEXT AS $$ - SELECT ok(NOT _ancestor_of( $2, $1, $3 ), $4 ); -$$ LANGUAGE SQL; - --- isnt_descendent_of( table, table, depth ) -CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, INT ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _ancestor_of( $2, $1, $3 ), - 'Table ' || quote_ident( $1 ) || ' should not be descendent ' || $3 || ' from ' || quote_ident( $2) - ); -$$ LANGUAGE SQL; - --- isnt_descendent_of( table, table, description ) -CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok(NOT _ancestor_of( $2, $1, NULL ), $3 ); -$$ LANGUAGE SQL; - --- isnt_descendent_of( table, table ) -CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _ancestor_of( $2, $1, NULL ), - 'Table ' || quote_ident( $1 ) || ' should not be a descendent of ' || quote_ident( $2) - ); -$$ LANGUAGE SQL; - --- is_normal_function( schema, function, args[], description ) -CREATE OR REPLACE FUNCTION is_normal_function ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, $3, _type_func('f', $1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- is_normal_function( schema, function, args[] ) -CREATE OR REPLACE FUNCTION is_normal_function( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _func_compare( - $1, $2, $3, - _type_func('f', $1, $2, $3), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should be a normal function' - ); -$$ LANGUAGE sql; - --- is_normal_function( schema, function, description ) -CREATE OR REPLACE FUNCTION is_normal_function ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, _type_func('f', $1, $2), $3 ); -$$ LANGUAGE SQL; - --- is_normal_function( schema, function ) -CREATE OR REPLACE FUNCTION is_normal_function( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _func_compare( - $1, $2, _type_func('f', $1, $2), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should be a normal function' - ); -$$ LANGUAGE sql; - --- is_normal_function( function, args[], description ) -CREATE OR REPLACE FUNCTION is_normal_function ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, $2, _type_func('f', $1, $2), $3 ); -$$ LANGUAGE SQL; - --- is_normal_function( function, args[] ) -CREATE OR REPLACE FUNCTION is_normal_function( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _func_compare( - NULL, $1, $2, _type_func('f', $1, $2), - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should be a normal function' - ); -$$ LANGUAGE sql; - --- is_normal_function( function, description ) -CREATE OR REPLACE FUNCTION is_normal_function( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, _type_func('f', $1), $2 ); -$$ LANGUAGE sql; - --- is_normal_function( function ) -CREATE OR REPLACE FUNCTION is_normal_function( NAME ) -RETURNS TEXT AS $$ - SELECT _func_compare( - NULL, $1, _type_func('f', $1), - 'Function ' || quote_ident($1) || '() should be a normal function' - ); -$$ LANGUAGE sql; - --- isnt_normal_function( schema, function, args[], description ) -CREATE OR REPLACE FUNCTION isnt_normal_function ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, $3, NOT _type_func('f', $1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- isnt_normal_function( schema, function, args[] ) -CREATE OR REPLACE FUNCTION isnt_normal_function( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _func_compare( - $1, $2, $3, NOT _type_func('f', $1, $2, $3), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should not be a normal function' - ); -$$ LANGUAGE sql; - --- isnt_normal_function( schema, function, description ) -CREATE OR REPLACE FUNCTION isnt_normal_function ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, NOT _type_func('f', $1, $2), $3 ); -$$ LANGUAGE SQL; - --- isnt_normal_function( schema, function ) -CREATE OR REPLACE FUNCTION isnt_normal_function( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _func_compare( - $1, $2, NOT _type_func('f', $1, $2), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not be a normal function' - ); -$$ LANGUAGE sql; - --- isnt_normal_function( function, args[], description ) -CREATE OR REPLACE FUNCTION isnt_normal_function ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, $2, NOT _type_func('f', $1, $2), $3 ); -$$ LANGUAGE SQL; - --- isnt_normal_function( function, args[] ) -CREATE OR REPLACE FUNCTION isnt_normal_function( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _func_compare( - NULL, $1, $2, - NOT _type_func('f', $1, $2), - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should not be a normal function' - ); -$$ LANGUAGE sql; - --- isnt_normal_function( function, description ) -CREATE OR REPLACE FUNCTION isnt_normal_function( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, NOT _type_func('f', $1), $2 ); -$$ LANGUAGE sql; - --- isnt_normal_function( function ) -CREATE OR REPLACE FUNCTION isnt_normal_function( NAME ) -RETURNS TEXT AS $$ - SELECT _func_compare( - NULL, $1, NOT _type_func('f', $1), - 'Function ' || quote_ident($1) || '() should not be a normal function' - ); -$$ LANGUAGE sql; - --- is_window( schema, function, args[], description ) -CREATE OR REPLACE FUNCTION is_window ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, $3, _type_func( 'w', $1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- is_window( schema, function, args[] ) -CREATE OR REPLACE FUNCTION is_window( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _func_compare( - $1, $2, $3, _type_func('w', $1, $2, $3), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should be a window function' - ); -$$ LANGUAGE sql; - --- is_window( schema, function, description ) -CREATE OR REPLACE FUNCTION is_window ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, _type_func('w', $1, $2), $3 ); -$$ LANGUAGE SQL; - --- is_window( schema, function ) -CREATE OR REPLACE FUNCTION is_window( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _func_compare( - $1, $2, _type_func('w', $1, $2), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should be a window function' - ); -$$ LANGUAGE sql; - --- is_window( function, args[], description ) -CREATE OR REPLACE FUNCTION is_window ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, $2, _type_func('w', $1, $2), $3 ); -$$ LANGUAGE SQL; - --- is_window( function, args[] ) -CREATE OR REPLACE FUNCTION is_window( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _func_compare( - NULL, $1, $2, _type_func('w', $1, $2), - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should be a window function' - ); -$$ LANGUAGE sql; - --- is_window( function, description ) -CREATE OR REPLACE FUNCTION is_window( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, _type_func('w', $1), $2 ); -$$ LANGUAGE sql; - --- is_window( function ) -CREATE OR REPLACE FUNCTION is_window( NAME ) -RETURNS TEXT AS $$ - SELECT _func_compare( - NULL, $1, _type_func('w', $1), - 'Function ' || quote_ident($1) || '() should be a window function' - ); -$$ LANGUAGE sql; - --- isnt_window( schema, function, args[], description ) -CREATE OR REPLACE FUNCTION isnt_window ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, $3, NOT _type_func('w', $1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- isnt_window( schema, function, args[] ) -CREATE OR REPLACE FUNCTION isnt_window( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _func_compare( - $1, $2, $3, NOT _type_func('w', $1, $2, $3), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should not be a window function' - ); -$$ LANGUAGE sql; - --- isnt_window( schema, function, description ) -CREATE OR REPLACE FUNCTION isnt_window ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, NOT _type_func('w', $1, $2), $3 ); -$$ LANGUAGE SQL; - --- isnt_window( schema, function ) -CREATE OR REPLACE FUNCTION isnt_window( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _func_compare( - $1, $2, NOT _type_func('w', $1, $2), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not be a window function' - ); -$$ LANGUAGE sql; - --- isnt_window( function, args[], description ) -CREATE OR REPLACE FUNCTION isnt_window ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, $2, NOT _type_func('w', $1, $2), $3 ); -$$ LANGUAGE SQL; - --- isnt_window( function, args[] ) -CREATE OR REPLACE FUNCTION isnt_window( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _func_compare( - NULL, $1, $2, NOT _type_func('w', $1, $2), - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should not be a window function' - ); -$$ LANGUAGE sql; - --- isnt_window( function, description ) -CREATE OR REPLACE FUNCTION isnt_window( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, NOT _type_func('w', $1), $2 ); -$$ LANGUAGE sql; - --- isnt_window( function ) -CREATE OR REPLACE FUNCTION isnt_window( NAME ) -RETURNS TEXT AS $$ - SELECT _func_compare( - NULL, $1, NOT _type_func('w', $1), - 'Function ' || quote_ident($1) || '() should not be a window function' - ); -$$ LANGUAGE sql; - --- is_procedure( schema, function, args[], description ) -CREATE OR REPLACE FUNCTION is_procedure ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, $3, _type_func( 'p', $1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- is_procedure( schema, function, args[] ) -CREATE OR REPLACE FUNCTION is_procedure( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _func_compare( - $1, $2, $3, _type_func('p', $1, $2, $3), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should be a procedure' - ); -$$ LANGUAGE sql; - --- is_procedure( schema, function, description ) -CREATE OR REPLACE FUNCTION is_procedure ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, _type_func('p', $1, $2), $3 ); -$$ LANGUAGE SQL; - --- is_procedure( schema, function ) -CREATE OR REPLACE FUNCTION is_procedure( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _func_compare( - $1, $2, _type_func('p', $1, $2), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should be a procedure' - ); -$$ LANGUAGE sql; - --- is_procedure( function, args[], description ) -CREATE OR REPLACE FUNCTION is_procedure ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, $2, _type_func('p', $1, $2), $3 ); -$$ LANGUAGE SQL; - --- is_procedure( function, args[] ) -CREATE OR REPLACE FUNCTION is_procedure( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _func_compare( - NULL, $1, $2, _type_func('p', $1, $2), - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should be a procedure' - ); -$$ LANGUAGE sql; - --- is_procedure( function, description ) -CREATE OR REPLACE FUNCTION is_procedure( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, _type_func('p', $1), $2 ); -$$ LANGUAGE sql; - --- is_procedure( function ) -CREATE OR REPLACE FUNCTION is_procedure( NAME ) -RETURNS TEXT AS $$ - SELECT _func_compare( - NULL, $1, _type_func('p', $1), - 'Function ' || quote_ident($1) || '() should be a procedure' - ); -$$ LANGUAGE sql; - --- isnt_procedure( schema, function, args[], description ) -CREATE OR REPLACE FUNCTION isnt_procedure ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, $3, NOT _type_func('p', $1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- isnt_procedure( schema, function, args[] ) -CREATE OR REPLACE FUNCTION isnt_procedure( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _func_compare( - $1, $2, $3, NOT _type_func('p', $1, $2, $3), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should not be a procedure' - ); -$$ LANGUAGE sql; - --- isnt_procedure( schema, function, description ) -CREATE OR REPLACE FUNCTION isnt_procedure ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, NOT _type_func('p', $1, $2), $3 ); -$$ LANGUAGE SQL; - --- isnt_procedure( schema, function ) -CREATE OR REPLACE FUNCTION isnt_procedure( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _func_compare( - $1, $2, NOT _type_func('p', $1, $2), - 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not be a procedure' - ); -$$ LANGUAGE sql; - --- isnt_procedure( function, args[], description ) -CREATE OR REPLACE FUNCTION isnt_procedure ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, $2, NOT _type_func('p', $1, $2), $3 ); -$$ LANGUAGE SQL; - --- isnt_procedure( function, args[] ) -CREATE OR REPLACE FUNCTION isnt_procedure( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _func_compare( - NULL, $1, $2, NOT _type_func('p', $1, $2), - 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should not be a procedure' - ); -$$ LANGUAGE sql; - --- isnt_procedure( function, description ) -CREATE OR REPLACE FUNCTION isnt_procedure( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, NOT _type_func('p', $1), $2 ); -$$ LANGUAGE sql; - --- isnt_procedure( function ) -CREATE OR REPLACE FUNCTION isnt_procedure( NAME ) -RETURNS TEXT AS $$ - SELECT _func_compare( - NULL, $1, NOT _type_func('p', $1), - 'Function ' || quote_ident($1) || '() should not be a procedure' - ); -$$ LANGUAGE sql; - ---added a three columns: "args", "returns", "langname" used in mock_func function -CREATE OR REPLACE VIEW tap_funky -AS -SELECT p.oid, - n.nspname AS schema, - p.proname AS name, - pg_get_userbyid(p.proowner) AS owner, - arg._types as args, - proc_return."returns", - p.prolang AS langoid, - p.proisstrict AS is_strict, - tap._prokind(p.oid) AS kind, - p.prosecdef AS is_definer, - p.proretset AS returns_set, - p.provolatile::character(1) AS volatility, - pg_function_is_visible(p.oid) AS is_visible, - l.lanname AS langname -FROM - pg_proc p -JOIN - pg_namespace n -ON - p.pronamespace = n.oid -LEFT JOIN - pg_language l -ON - l.oid = p.prolang -left join lateral ( - select string_agg(nullif(_type, '')::regtype::text, ', ') as _types - from unnest(regexp_split_to_array(p.proargtypes::text, ' ')) as _type -) as arg on true - LEFT JOIN LATERAL ( - SELECT (n.nspname::text || '.'::text) || p.proname::text AS qualified -) proc_name ON true -left join lateral ( - select - case - when n.nspname != 'pg_catalog' - then pg_get_function_result((concat(proc_name.qualified, '(', arg._types, ')')::regprocedure)::oid) - else null - end AS "returns" -) as proc_return on true; - ---this procedure creates a mock in place of a real function -create or replace procedure mock_func( - in _func_schema text - , in _func_name text - , in _func_args text - , in _return_value anyelement -) ---creates mock in place of a real function - LANGUAGE plpgsql -AS $procedure$ -declare - _mock_ddl text; - _func_result_type text; - _func_qualified_name text; - _func_language text; -begin - select - "returns" - , langname - into - _func_result_type - , _func_language - from - tap_funky - where - "schema" = _func_schema - and "name" = _func_name; - - _mock_ddl = ' - create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' - RETURNS ' || _func_result_type || ' - LANGUAGE ' || _func_language || ' - AS $function$ - select ' || quote_nullable(_return_value) || '::' || pg_typeof(_return_value) || '; - $function$;'; - execute _mock_ddl; -end $procedure$; - -CREATE OR REPLACE PROCEDURE fake_table( - IN _table_schema text[], - IN _table_name text[], - IN _make_table_empty boolean default false, - IN _drop_not_null boolean DEFAULT false, - IN _drop_collation boolean DEFAULT false -) ---It frees a table from any constraint (we call such a table as a fake) ---faked table is a full copy of _table_name, but has no any constraint ---without foreign and primary things you can do whatever you want in testing context - LANGUAGE plpgsql -AS $procedure$ -declare - _table record; - _fk_table record; - _fake_ddl text; - _not_null_ddl text; -begin - for _table in - select - quote_ident(table_schema) table_schema, - quote_ident(table_name) table_name, - table_schema table_schema_l, - table_name table_name_l - from - unnest(_table_schema, _table_name) as t(table_schema, table_name) - loop - for _fk_table in - -- collect all table's relations including primary key and unique constraint - select distinct * - from ( - select - fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord - from - pg_all_foreign_keys - where - fk_schema_name = _table.table_schema_l and fk_table_name = _table.table_name_l - union all - select - fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord - from - pg_all_foreign_keys - where - pk_schema_name = _table.table_schema_l and pk_table_name = _table.table_name_l - union all - select - table_schema, table_name, constraint_name, 2 as ord - from - information_schema.table_constraints - where - table_schema = _table.table_schema_l - and table_name = _table.table_name_l - and constraint_type in ('PRIMARY KEY', 'UNIQUE') - ) as t - order by ord - loop - _fake_ddl = 'alter table ' || _fk_table.table_schema || '.' || _fk_table.table_name || ' - drop constraint ' || _fk_table.constraint_name || ';'; - execute _fake_ddl; - end loop; - - --make table empty - if _make_table_empty then - _fake_ddl = 'truncate table ' || _table.table_schema || '.' || _table.table_name || ';'; - execute _fake_ddl; - end if; - - --Free table from not null constraints - _fake_ddl = 'alter table ' || _table.table_schema || '.' || _table.table_name || ' '; - if _drop_not_null then - select - string_agg('alter column ' || t.attname || ' drop not null', ', ') - into - _not_null_ddl - from - pg_catalog.pg_attribute t - where t.attrelid = (_table.table_schema || '.' || _table.table_name)::regclass - and t.attnum > 0 and attnotnull; - - _fake_ddl = _fake_ddl || _not_null_ddl || ';'; - else - _fake_ddl = null; - end if; - - if _fake_ddl is not null then - execute _fake_ddl; - end if; - end loop; -end $procedure$; - -CREATE OR REPLACE FUNCTION _runner(text[], text[], text[], text[], text[]) - RETURNS SETOF text - LANGUAGE plpgsql -AS $function$ -DECLARE - startup ALIAS FOR $1; - shutdown ALIAS FOR $2; - setup ALIAS FOR $3; - teardown ALIAS FOR $4; - tests ALIAS FOR $5; - tap TEXT; - tfaild INTEGER := 0; - ffaild INTEGER := 0; - tnumb INTEGER := 0; - fnumb INTEGER := 0; - tok BOOLEAN := TRUE; -BEGIN - BEGIN - -- No plan support. - PERFORM * FROM no_plan(); - FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; - EXCEPTION - -- Catch all exceptions and simply rethrow custom exceptions. This - -- will roll back everything in the above block. - WHEN raise_exception THEN RAISE EXCEPTION '%', SQLERRM; - END; - - -- Record how startup tests have failed. - tfaild := num_failed(); - - FOR i IN 1..COALESCE(array_upper(tests, 1), 0) LOOP - - -- What subtest are we running? - RETURN NEXT diag_test_name('Subtest: ' || tests[i]); - - -- Reset the results. - tok := TRUE; - tnumb := COALESCE(_get('curr_test'), 0); - - IF tnumb > 0 THEN - EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; - PERFORM _set('curr_test', 0); - PERFORM _set('failed', 0); - END IF; - - DECLARE - errstate text; - errmsg text; - detail text; - hint text; - context text; - schname text; - tabname text; - colname text; - chkname text; - typname text; - BEGIN - BEGIN - -- Run the setup functions. - FOR tap IN SELECT * FROM _runem(setup, false) LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Create a temp table to collect call count - execute 'create temp table call_count( - routine_schema text not null - , routine_name text not null - , call_cnt int - ) ON COMMIT DROP;'; - - -- Run the actual test function. - FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Run the teardown functions. - FOR tap IN SELECT * FROM _runem(teardown, false) LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Emit the plan. - fnumb := COALESCE(_get('curr_test'), 0); - RETURN NEXT ' 1..' || fnumb; - - -- Emit any error messages. - IF fnumb = 0 THEN - RETURN NEXT ' # No tests run!'; - tok = false; - ELSE - -- Report failures. - ffaild := num_failed(); - IF ffaild > 0 THEN - tok := FALSE; - RETURN NEXT ' ' || diag( - 'Looks like you failed ' || ffaild || ' test' || - CASE ffaild WHEN 1 THEN '' ELSE 's' END - || ' of ' || fnumb - ); - END IF; - END IF; - - EXCEPTION WHEN OTHERS THEN - -- Something went wrong. Record that fact. - errstate := SQLSTATE; - errmsg := SQLERRM; - GET STACKED DIAGNOSTICS - detail = PG_EXCEPTION_DETAIL, - hint = PG_EXCEPTION_HINT, - context = PG_EXCEPTION_CONTEXT, - schname = SCHEMA_NAME, - tabname = TABLE_NAME, - colname = COLUMN_NAME, - chkname = CONSTRAINT_NAME, - typname = PG_DATATYPE_NAME; - END; - - -- Always raise an exception to rollback any changes. - RAISE EXCEPTION '__TAP_ROLLBACK__'; - - EXCEPTION WHEN raise_exception THEN - IF errmsg IS NOT NULL THEN - -- Something went wrong. Emit the error message. - tok := FALSE; - RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( - errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname - )), '^', ' ', 'gn'); - errmsg := NULL; - END IF; - END; - - -- Restore the sequence. - EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; - PERFORM _set('curr_test', tnumb); - PERFORM _set('failed', tfaild); - - -- Record this test. - RETURN NEXT ok(tok, tests[i]); - IF NOT tok THEN tfaild := tfaild + 1; END IF; - - END LOOP; - - -- Run the shutdown functions. - FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; - - -- Finish up. - FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP - RETURN NEXT tap; - END LOOP; - - -- Clean up and return. - PERFORM _cleanup(); - RETURN; -END; -$function$; - -create or replace procedure willing_count_calls_of(_proc_schema text, _proc_name text, _proc_args text) ---it counts of calls of a routine during execution - LANGUAGE plpgsql -AS $procedure$ -declare - _ddl text; -begin - insert into call_count(routine_schema, routine_name, call_cnt) values(_proc_schema, _proc_name, 0); - _ddl = ' - create or replace procedure ' || quote_ident(_proc_schema) || '.' || quote_ident(_proc_name) || _proc_args || ' - LANGUAGE plpgsql - AS $proc$ - begin - update call_count set call_cnt = call_cnt + 1 - where routine_schema = ' || quote_literal(_proc_schema) || ' - and routine_name = ' || quote_literal(_proc_name) || '; - end - $proc$;'; - execute _ddl; -end -$procedure$; - -create or replace function called_once(_proc_schema text, _proc_name text) ---Insures that a routine have been called only once -returns setof text as $$ -begin - return query select called_times(1, _proc_schema, _proc_name); -end $$ -LANGUAGE plpgsql; - -create or replace function called_times(_call_count int, _proc_schema text, _proc_name text) ---Insures that a routine have been called specified times -returns setof text as $$ -declare - _actual_call_count int; -begin - select - call_cnt - into - _actual_call_count - from - call_count - where - routine_schema = _proc_schema - and routine_name = _proc_name; - - return query select ok( - _actual_call_count = _call_count - , format('routine %L.%L must have been called %L times', _proc_schema, _proc_name, _call_count) - ); -end $$ -LANGUAGE plpgsql; - -create or replace function drop_prepared_statement(_statement_name text) ---It drops a prepared statement to be sure you can run a test many times -returns bool as $$ -begin - if exists(select * from pg_prepared_statements where "name" = _statement_name) then - EXECUTE format('deallocate %I;', _statement_name); - return true; - end if; - return false; -end -$$ -language plpgsql; - -create or replace procedure print_table_as_json(in _table_schema text, in _table_name text) - language plpgsql -AS $procedure$ -declare - _ddl text; - _json text; - _columns text; ---returns a query which you can execute and see your table as normal dataset ---note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows -begin - _ddl = ' - select json_agg( - array(select ' || quote_ident(_table_name) || ' from ' || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || ' limit 1000 - )) as j;'; - execute _ddl into _json; - _json = '[' || ltrim(rtrim(_json::text, ']'), '[') || ']'; - - select string_agg(concat(c.column_name, ' ', case when lower(c.data_type) = 'array' then e.data_type || '[]' else c.data_type end), ', ') - into _columns - from information_schema."columns" c - left join information_schema.element_types e - on ((c.table_catalog, c.table_schema, c.table_name, 'TABLE', c.dtd_identifier) - = (e.object_catalog, e.object_schema, e.object_name, e.object_type, e.collection_type_identifier)) - where c.table_schema = _table_schema - and c.table_name = _table_name; - - _json = $$select * from /*$$ || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || $$*/ json_to_recordset('$$ || _json || $$') as t($$ || _columns || $$)$$; - raise notice '%', _json; -end $procedure$; \ No newline at end of file diff --git a/sql/pgtap--1.3.3--1.3.4.sql b/sql/pgtap--1.3.3--1.3.4.sql index 9cd059f4f..f5ab93b37 100644 --- a/sql/pgtap--1.3.3--1.3.4.sql +++ b/sql/pgtap--1.3.3--1.3.4.sql @@ -15,3 +15,421 @@ RETURNS TEXT AS $$ 'Composite type ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); $$ LANGUAGE SQL; + +--added a three columns: "args", "returns", "langname" used in mock_func function +CREATE OR REPLACE VIEW tap_funky +AS +SELECT p.oid, + n.nspname AS schema, + p.proname AS name, + pg_get_userbyid(p.proowner) AS owner, + arg._types as args, + proc_return."returns", + p.prolang AS langoid, + p.proisstrict AS is_strict, + tap._prokind(p.oid) AS kind, + p.prosecdef AS is_definer, + p.proretset AS returns_set, + p.provolatile::character(1) AS volatility, + pg_function_is_visible(p.oid) AS is_visible, + l.lanname AS langname +FROM + pg_proc p +JOIN + pg_namespace n +ON + p.pronamespace = n.oid +LEFT JOIN + pg_language l +ON + l.oid = p.prolang +left join lateral ( + select string_agg(nullif(_type, '')::regtype::text, ', ') as _types + from unnest(regexp_split_to_array(p.proargtypes::text, ' ')) as _type +) as arg on true + LEFT JOIN LATERAL ( + SELECT (n.nspname::text || '.'::text) || p.proname::text AS qualified +) proc_name ON true +left join lateral ( + select + case + when n.nspname != 'pg_catalog' + then pg_get_function_result((concat(proc_name.qualified, '(', arg._types, ')')::regprocedure)::oid) + else null + end AS "returns" +) as proc_return on true; + +--this procedure creates a mock in place of a real function +create or replace procedure mock_func( + in _func_schema text + , in _func_name text + , in _func_args text + , in _return_value anyelement +) +--creates mock in place of a real function + LANGUAGE plpgsql +AS $procedure$ +declare + _mock_ddl text; + _func_result_type text; + _func_qualified_name text; + _func_language text; +begin + select + "returns" + , langname + into + _func_result_type + , _func_language + from + tap_funky + where + "schema" = _func_schema + and "name" = _func_name; + + _mock_ddl = ' + create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' + RETURNS ' || _func_result_type || ' + LANGUAGE ' || _func_language || ' + AS $function$ + select ' || quote_nullable(_return_value) || '::' || pg_typeof(_return_value) || '; + $function$;'; + execute _mock_ddl; +end $procedure$; + +CREATE OR REPLACE PROCEDURE fake_table( + IN _table_schema text[], + IN _table_name text[], + in _make_table_empty boolean default false, + IN _drop_not_null boolean DEFAULT false, + IN _drop_collation boolean DEFAULT false +) +--It frees a table from any constraint (we call such a table as a fake) +--faked table is a full copy of _table_name, but has no any constraint +--without foreign and primary things you can do whatever you want in testing context + LANGUAGE plpgsql +AS $procedure$ +declare + _table record; + _fk_table record; + _fake_ddl text; + _not_null_ddl text; +begin + for _table in + select + quote_ident(table_schema) table_schema, + quote_ident(table_name) table_name, + table_schema table_schema_l, + table_name table_name_l + from + unnest(_table_schema, _table_name) as t(table_schema, table_name) + loop + for _fk_table in + -- collect all table's relations including primary key and unique constraint + select distinct * + from ( + select + fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord + from + pg_all_foreign_keys + where + fk_schema_name = _table.table_schema_l and fk_table_name = _table.table_name_l + union all + select + fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord + from + pg_all_foreign_keys + where + pk_schema_name = _table.table_schema_l and pk_table_name = _table.table_name_l + union all + select + table_schema, table_name, constraint_name, 2 as ord + from + information_schema.table_constraints + where + table_schema = _table.table_schema_l + and table_name = _table.table_name_l + and constraint_type in ('PRIMARY KEY', 'UNIQUE') + ) as t + order by ord + loop + _fake_ddl = 'alter table ' || _fk_table.table_schema || '.' || _fk_table.table_name || ' + drop constraint ' || _fk_table.constraint_name || ';'; + execute _fake_ddl; + end loop; + + if _make_table_empty then + _fake_ddl = 'truncate table ' || _table.table_schema || '.' || _table.table_name || ';'; + execute _fake_ddl; + end if; + + --Free table from not null constraints + _fake_ddl = 'alter table ' || _table.table_schema || '.' || _table.table_name || ' '; + if _drop_not_null then + select + string_agg('alter column ' || t.attname || ' drop not null', ', ') + into + _not_null_ddl + from + pg_catalog.pg_attribute t + where t.attrelid = (_table.table_schema || '.' || _table.table_name)::regclass + and t.attnum > 0 and attnotnull; + + _fake_ddl = _fake_ddl || _not_null_ddl || ';'; + else + _fake_ddl = null; + end if; + + if _fake_ddl is not null then + execute _fake_ddl; + end if; + end loop; +end $procedure$; + +CREATE OR REPLACE FUNCTION _runner(text[], text[], text[], text[], text[]) + RETURNS SETOF text + LANGUAGE plpgsql +AS $function$ +DECLARE + startup ALIAS FOR $1; + shutdown ALIAS FOR $2; + setup ALIAS FOR $3; + teardown ALIAS FOR $4; + tests ALIAS FOR $5; + tap TEXT; + tfaild INTEGER := 0; + ffaild INTEGER := 0; + tnumb INTEGER := 0; + fnumb INTEGER := 0; + tok BOOLEAN := TRUE; +BEGIN + BEGIN + -- No plan support. + PERFORM * FROM no_plan(); + FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; + EXCEPTION + -- Catch all exceptions and simply rethrow custom exceptions. This + -- will roll back everything in the above block. + WHEN raise_exception THEN RAISE EXCEPTION '%', SQLERRM; + END; + + -- Record how startup tests have failed. + tfaild := num_failed(); + + FOR i IN 1..COALESCE(array_upper(tests, 1), 0) LOOP + + -- What subtest are we running? + RETURN NEXT diag_test_name('Subtest: ' || tests[i]); + + -- Reset the results. + tok := TRUE; + tnumb := COALESCE(_get('curr_test'), 0); + + IF tnumb > 0 THEN + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; + PERFORM _set('curr_test', 0); + PERFORM _set('failed', 0); + END IF; + + DECLARE + errstate text; + errmsg text; + detail text; + hint text; + context text; + schname text; + tabname text; + colname text; + chkname text; + typname text; + BEGIN + BEGIN + -- Run the setup functions. + FOR tap IN SELECT * FROM _runem(setup, false) LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Create a temp table to collect call count + execute 'create temp table call_count( + routine_schema text not null + , routine_name text not null + , call_cnt int + ) ON COMMIT DROP;'; + + -- Run the actual test function. + FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Run the teardown functions. + FOR tap IN SELECT * FROM _runem(teardown, false) LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Emit the plan. + fnumb := COALESCE(_get('curr_test'), 0); + RETURN NEXT ' 1..' || fnumb; + + -- Emit any error messages. + IF fnumb = 0 THEN + RETURN NEXT ' # No tests run!'; + tok = false; + ELSE + -- Report failures. + ffaild := num_failed(); + IF ffaild > 0 THEN + tok := FALSE; + RETURN NEXT ' ' || diag( + 'Looks like you failed ' || ffaild || ' test' || + CASE ffaild WHEN 1 THEN '' ELSE 's' END + || ' of ' || fnumb + ); + END IF; + END IF; + + EXCEPTION WHEN OTHERS THEN + -- Something went wrong. Record that fact. + errstate := SQLSTATE; + errmsg := SQLERRM; + GET STACKED DIAGNOSTICS + detail = PG_EXCEPTION_DETAIL, + hint = PG_EXCEPTION_HINT, + context = PG_EXCEPTION_CONTEXT, + schname = SCHEMA_NAME, + tabname = TABLE_NAME, + colname = COLUMN_NAME, + chkname = CONSTRAINT_NAME, + typname = PG_DATATYPE_NAME; + END; + + -- Always raise an exception to rollback any changes. + RAISE EXCEPTION '__TAP_ROLLBACK__'; + + EXCEPTION WHEN raise_exception THEN + IF errmsg IS NOT NULL THEN + -- Something went wrong. Emit the error message. + tok := FALSE; + RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( + errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname + )), '^', ' ', 'gn'); + errmsg := NULL; + END IF; + END; + + -- Restore the sequence. + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; + PERFORM _set('curr_test', tnumb); + PERFORM _set('failed', tfaild); + + -- Record this test. + RETURN NEXT ok(tok, tests[i]); + IF NOT tok THEN tfaild := tfaild + 1; END IF; + + END LOOP; + + -- Run the shutdown functions. + FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; + + -- Finish up. + FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP + RETURN NEXT tap; + END LOOP; + + -- Clean up and return. + PERFORM _cleanup(); + RETURN; +END; +$function$; + +create or replace procedure willing_count_calls_of(_proc_schema text, _proc_name text, _proc_args text) + LANGUAGE plpgsql +AS $procedure$ +declare + _ddl text; +begin + insert into call_count(routine_schema, routine_name, call_cnt) values(_proc_schema, _proc_name, 0); + _ddl = ' + create or replace procedure ' || quote_ident(_proc_schema) || '.' || quote_ident(_proc_name) || _proc_args || ' + LANGUAGE plpgsql + AS $proc$ + begin + update call_count set call_cnt = call_cnt + 1 + where routine_schema = ' || quote_literal(_proc_schema) || ' + and routine_name = ' || quote_literal(_proc_name) || '; + end + $proc$;'; + raise notice '%', _ddl; + execute _ddl; +end +$procedure$; + +create or replace function called_once(_proc_schema text, _proc_name text) +returns setof text as $$ +begin + return query select called_times(1, _proc_schema, _proc_name); +end $$ +LANGUAGE plpgsql; + +create or replace function called_times(_call_count int, _proc_schema text, _proc_name text) +returns setof text as $$ +declare + _actual_call_count int; +begin + select + call_cnt + into + _actual_call_count + from + call_count + where + routine_schema = _proc_schema + and routine_name = _proc_name; + + return query select ok( + _actual_call_count = _call_count + , format('routine %L.%L must have been called %L times', _proc_schema, _proc_name, _call_count) + ); +end $$ +LANGUAGE plpgsql; + +create or replace function drop_prepared_statement(_statement_name text) +returns bool as $$ +begin + if exists(select * from pg_prepared_statements where "name" = _statement_name) then + EXECUTE format('deallocate %I;', _statement_name); + return true; + end if; + return false; +end +$$ +language plpgsql; + +create or replace procedure print_table_as_json(in _table_schema text, in _table_name text) + language plpgsql +AS $procedure$ +declare + _ddl text; + _json text; + _columns text; +--returns a query which you can execute and see your table as normal dataset +--note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows +begin + _ddl = ' + select json_agg( + array(select ' || quote_ident(_table_name) || ' from ' || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || ' limit 1000 + )) as j;'; + execute _ddl into _json; + _json = '[' || ltrim(rtrim(_json::text, ']'), '[') || ']'; + + select string_agg(concat(c.column_name, ' ', case when lower(c.data_type) = 'array' then e.data_type || '[]' else c.data_type end), ', ') + into _columns + from information_schema."columns" c + left join information_schema.element_types e + on ((c.table_catalog, c.table_schema, c.table_name, 'TABLE', c.dtd_identifier) + = (e.object_catalog, e.object_schema, e.object_name, e.object_type, e.collection_type_identifier)) + where c.table_schema = _table_schema + and c.table_name = _table_name; + + _json = $$select * from /*$$ || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || $$*/ json_to_recordset('$$ || _json || $$') as t($$ || _columns || $$)$$; + raise notice '%', _json; +end $procedure$; diff --git a/sql/pgtap--unpackaged--0.91.0.sql b/sql/pgtap--unpackaged--0.91.0.sql index e8875c32f..cec4c09a2 100644 --- a/sql/pgtap--unpackaged--0.91.0.sql +++ b/sql/pgtap--unpackaged--0.91.0.sql @@ -712,3 +712,11 @@ ALTER EXTENSION pgtap ADD FUNCTION columns_are( NAME, NAME[] ); ALTER EXTENSION pgtap ADD FUNCTION _get_db_owner( NAME ); ALTER EXTENSION pgtap ADD FUNCTION db_owner_is ( NAME, NAME, TEXT ); ALTER EXTENSION pgtap ADD FUNCTION db_owner_is ( NAME, NAME ); + +ALTER EXTENSION pgtap ADD PROCEDURE mock_func( TEXT, TEXT, TEXT, ANYELEMENT ); +ALTER EXTENSION pgtap ADD PROCEDURE fake_table( TEXT[], TEXT[], BOOL, BOOL ); +ALTER EXTENSION pgtap ADD PROCEDURE willing_count_calls_of( TEXT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION called_once( TEXT , TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION called_times( INT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION drop_prepared_statement( TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION print_table_as_json( TEXT, TEXT ); \ No newline at end of file diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 5f4cd1592..ed94e3728 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -11446,3 +11446,425 @@ RETURNS TEXT AS $$ 'Function ' || quote_ident($1) || '() should not be a procedure' ); $$ LANGUAGE sql; + +--added a three columns: "args", "returns", "langname" used in mock_func function +CREATE OR REPLACE VIEW tap_funky +AS +SELECT p.oid, + n.nspname AS schema, + p.proname AS name, + pg_get_userbyid(p.proowner) AS owner, + arg._types as args, + proc_return."returns", + p.prolang AS langoid, + p.proisstrict AS is_strict, + tap._prokind(p.oid) AS kind, + p.prosecdef AS is_definer, + p.proretset AS returns_set, + p.provolatile::character(1) AS volatility, + pg_function_is_visible(p.oid) AS is_visible, + l.lanname AS langname +FROM + pg_proc p +JOIN + pg_namespace n +ON + p.pronamespace = n.oid +LEFT JOIN + pg_language l +ON + l.oid = p.prolang +left join lateral ( + select string_agg(nullif(_type, '')::regtype::text, ', ') as _types + from unnest(regexp_split_to_array(p.proargtypes::text, ' ')) as _type +) as arg on true + LEFT JOIN LATERAL ( + SELECT (n.nspname::text || '.'::text) || p.proname::text AS qualified +) proc_name ON true +left join lateral ( + select + case + when n.nspname != 'pg_catalog' + then pg_get_function_result((concat(proc_name.qualified, '(', arg._types, ')')::regprocedure)::oid) + else null + end AS "returns" +) as proc_return on true; + +--this procedure creates a mock in place of a real function +create or replace procedure mock_func( + in _func_schema text + , in _func_name text + , in _func_args text + , in _return_value anyelement +) +--creates mock in place of a real function + LANGUAGE plpgsql +AS $procedure$ +declare + _mock_ddl text; + _func_result_type text; + _func_qualified_name text; + _func_language text; +begin + select + "returns" + , langname + into + _func_result_type + , _func_language + from + tap_funky + where + "schema" = _func_schema + and "name" = _func_name; + + _mock_ddl = ' + create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' + RETURNS ' || _func_result_type || ' + LANGUAGE ' || _func_language || ' + AS $function$ + select ' || quote_nullable(_return_value) || '::' || pg_typeof(_return_value) || '; + $function$;'; + execute _mock_ddl; +end $procedure$; + +CREATE OR REPLACE PROCEDURE fake_table( + IN _table_schema text[], + IN _table_name text[], + IN _make_table_empty boolean default false, + IN _drop_not_null boolean DEFAULT false, + IN _drop_collation boolean DEFAULT false +) +--It frees a table from any constraint (we call such a table as a fake) +--faked table is a full copy of _table_name, but has no any constraint +--without foreign and primary things you can do whatever you want in testing context + LANGUAGE plpgsql +AS $procedure$ +declare + _table record; + _fk_table record; + _fake_ddl text; + _not_null_ddl text; +begin + for _table in + select + quote_ident(table_schema) table_schema, + quote_ident(table_name) table_name, + table_schema table_schema_l, + table_name table_name_l + from + unnest(_table_schema, _table_name) as t(table_schema, table_name) + loop + for _fk_table in + -- collect all table's relations including primary key and unique constraint + select distinct * + from ( + select + fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord + from + pg_all_foreign_keys + where + fk_schema_name = _table.table_schema_l and fk_table_name = _table.table_name_l + union all + select + fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord + from + pg_all_foreign_keys + where + pk_schema_name = _table.table_schema_l and pk_table_name = _table.table_name_l + union all + select + table_schema, table_name, constraint_name, 2 as ord + from + information_schema.table_constraints + where + table_schema = _table.table_schema_l + and table_name = _table.table_name_l + and constraint_type in ('PRIMARY KEY', 'UNIQUE') + ) as t + order by ord + loop + _fake_ddl = 'alter table ' || _fk_table.table_schema || '.' || _fk_table.table_name || ' + drop constraint ' || _fk_table.constraint_name || ';'; + execute _fake_ddl; + end loop; + + --make table empty + if _make_table_empty then + _fake_ddl = 'truncate table ' || _table.table_schema || '.' || _table.table_name || ';'; + execute _fake_ddl; + end if; + + --Free table from not null constraints + _fake_ddl = 'alter table ' || _table.table_schema || '.' || _table.table_name || ' '; + if _drop_not_null then + select + string_agg('alter column ' || t.attname || ' drop not null', ', ') + into + _not_null_ddl + from + pg_catalog.pg_attribute t + where t.attrelid = (_table.table_schema || '.' || _table.table_name)::regclass + and t.attnum > 0 and attnotnull; + + _fake_ddl = _fake_ddl || _not_null_ddl || ';'; + else + _fake_ddl = null; + end if; + + if _fake_ddl is not null then + execute _fake_ddl; + end if; + end loop; +end $procedure$; + +CREATE OR REPLACE FUNCTION _runner(text[], text[], text[], text[], text[]) + RETURNS SETOF text + LANGUAGE plpgsql +AS $function$ +DECLARE + startup ALIAS FOR $1; + shutdown ALIAS FOR $2; + setup ALIAS FOR $3; + teardown ALIAS FOR $4; + tests ALIAS FOR $5; + tap TEXT; + tfaild INTEGER := 0; + ffaild INTEGER := 0; + tnumb INTEGER := 0; + fnumb INTEGER := 0; + tok BOOLEAN := TRUE; +BEGIN + BEGIN + -- No plan support. + PERFORM * FROM no_plan(); + FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; + EXCEPTION + -- Catch all exceptions and simply rethrow custom exceptions. This + -- will roll back everything in the above block. + WHEN raise_exception THEN RAISE EXCEPTION '%', SQLERRM; + END; + + -- Record how startup tests have failed. + tfaild := num_failed(); + + FOR i IN 1..COALESCE(array_upper(tests, 1), 0) LOOP + + -- What subtest are we running? + RETURN NEXT diag_test_name('Subtest: ' || tests[i]); + + -- Reset the results. + tok := TRUE; + tnumb := COALESCE(_get('curr_test'), 0); + + IF tnumb > 0 THEN + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; + PERFORM _set('curr_test', 0); + PERFORM _set('failed', 0); + END IF; + + DECLARE + errstate text; + errmsg text; + detail text; + hint text; + context text; + schname text; + tabname text; + colname text; + chkname text; + typname text; + BEGIN + BEGIN + -- Run the setup functions. + FOR tap IN SELECT * FROM _runem(setup, false) LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Create a temp table to collect call count + execute 'create temp table call_count( + routine_schema text not null + , routine_name text not null + , call_cnt int + ) ON COMMIT DROP;'; + + -- Run the actual test function. + FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Run the teardown functions. + FOR tap IN SELECT * FROM _runem(teardown, false) LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; + + -- Emit the plan. + fnumb := COALESCE(_get('curr_test'), 0); + RETURN NEXT ' 1..' || fnumb; + + -- Emit any error messages. + IF fnumb = 0 THEN + RETURN NEXT ' # No tests run!'; + tok = false; + ELSE + -- Report failures. + ffaild := num_failed(); + IF ffaild > 0 THEN + tok := FALSE; + RETURN NEXT ' ' || diag( + 'Looks like you failed ' || ffaild || ' test' || + CASE ffaild WHEN 1 THEN '' ELSE 's' END + || ' of ' || fnumb + ); + END IF; + END IF; + + EXCEPTION WHEN OTHERS THEN + -- Something went wrong. Record that fact. + errstate := SQLSTATE; + errmsg := SQLERRM; + GET STACKED DIAGNOSTICS + detail = PG_EXCEPTION_DETAIL, + hint = PG_EXCEPTION_HINT, + context = PG_EXCEPTION_CONTEXT, + schname = SCHEMA_NAME, + tabname = TABLE_NAME, + colname = COLUMN_NAME, + chkname = CONSTRAINT_NAME, + typname = PG_DATATYPE_NAME; + END; + + -- Always raise an exception to rollback any changes. + RAISE EXCEPTION '__TAP_ROLLBACK__'; + + EXCEPTION WHEN raise_exception THEN + IF errmsg IS NOT NULL THEN + -- Something went wrong. Emit the error message. + tok := FALSE; + RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( + errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname + )), '^', ' ', 'gn'); + errmsg := NULL; + END IF; + END; + + -- Restore the sequence. + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; + PERFORM _set('curr_test', tnumb); + PERFORM _set('failed', tfaild); + + -- Record this test. + RETURN NEXT ok(tok, tests[i]); + IF NOT tok THEN tfaild := tfaild + 1; END IF; + + END LOOP; + + -- Run the shutdown functions. + FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; + + -- Finish up. + FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP + RETURN NEXT tap; + END LOOP; + + -- Clean up and return. + PERFORM _cleanup(); + RETURN; +END; +$function$; + +create or replace procedure willing_count_calls_of(_proc_schema text, _proc_name text, _proc_args text) +--it counts of calls of a routine during execution + LANGUAGE plpgsql +AS $procedure$ +declare + _ddl text; +begin + insert into call_count(routine_schema, routine_name, call_cnt) values(_proc_schema, _proc_name, 0); + _ddl = ' + create or replace procedure ' || quote_ident(_proc_schema) || '.' || quote_ident(_proc_name) || _proc_args || ' + LANGUAGE plpgsql + AS $proc$ + begin + update call_count set call_cnt = call_cnt + 1 + where routine_schema = ' || quote_literal(_proc_schema) || ' + and routine_name = ' || quote_literal(_proc_name) || '; + end + $proc$;'; + execute _ddl; +end +$procedure$; + +create or replace function called_once(_proc_schema text, _proc_name text) +--Insures that a routine have been called only once +returns setof text as $$ +begin + return query select called_times(1, _proc_schema, _proc_name); +end $$ +LANGUAGE plpgsql; + +create or replace function called_times(_call_count int, _proc_schema text, _proc_name text) +--Insures that a routine have been called specified times +returns setof text as $$ +declare + _actual_call_count int; +begin + select + call_cnt + into + _actual_call_count + from + call_count + where + routine_schema = _proc_schema + and routine_name = _proc_name; + + return query select ok( + _actual_call_count = _call_count + , format('routine %L.%L must have been called %L times', _proc_schema, _proc_name, _call_count) + ); +end $$ +LANGUAGE plpgsql; + +create or replace function drop_prepared_statement(_statement_name text) +--It drops a prepared statement to be sure you can run a test many times +returns bool as $$ +begin + if exists(select * from pg_prepared_statements where "name" = _statement_name) then + EXECUTE format('deallocate %I;', _statement_name); + return true; + end if; + return false; +end +$$ +language plpgsql; + +create or replace procedure print_table_as_json(in _table_schema text, in _table_name text) + language plpgsql +AS $procedure$ +declare + _ddl text; + _json text; + _columns text; +--returns a query which you can execute and see your table as normal dataset +--note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows +begin + _ddl = ' + select json_agg( + array(select ' || quote_ident(_table_name) || ' from ' || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || ' limit 1000 + )) as j;'; + execute _ddl into _json; + _json = '[' || ltrim(rtrim(_json::text, ']'), '[') || ']'; + + select string_agg(concat(c.column_name, ' ', case when lower(c.data_type) = 'array' then e.data_type || '[]' else c.data_type end), ', ') + into _columns + from information_schema."columns" c + left join information_schema.element_types e + on ((c.table_catalog, c.table_schema, c.table_name, 'TABLE', c.dtd_identifier) + = (e.object_catalog, e.object_schema, e.object_name, e.object_type, e.collection_type_identifier)) + where c.table_schema = _table_schema + and c.table_name = _table_name; + + _json = $$select * from /*$$ || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || $$*/ json_to_recordset('$$ || _json || $$') as t($$ || _columns || $$)$$; + raise notice '%', _json; +end $procedure$; \ No newline at end of file From 9ab57caf765dc37724da1049e66e4ac50e4f5d15 Mon Sep 17 00:00:00 2001 From: malyutinvn Date: Thu, 2 Jan 2025 08:11:12 +0300 Subject: [PATCH 04/20] added example how to use mock and fake functions --- examples/mocking_and_faking.sql | 331 ++++++++++++++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 examples/mocking_and_faking.sql diff --git a/examples/mocking_and_faking.sql b/examples/mocking_and_faking.sql new file mode 100644 index 000000000..74f937f83 --- /dev/null +++ b/examples/mocking_and_faking.sql @@ -0,0 +1,331 @@ +create schema pgconf; + +set search_path to public, tap; + +drop table pgconf.osv; +drop table pgconf.transactions; +drop table pgconf.analytic; +drop table pgconf.account; + +create table pgconf.account( + id int generated always as identity primary key + , parent_id int + , num text + , constraint account_01_fk foreign key(parent_id) references pgconf.account(id) +); + +create table pgconf.analytic(id int generated always as identity primary key, subconto text); + +create table pgconf.transactions( + account_num text + , subconto_1 text + , subconto_2 text + , subconto_3 text + , amount_dt numeric(15, 2) + , amount_ct numeric(15, 2) +); + +create table pgconf.osv( + account_id int + , subconto_1_id int + , subconto_2_id int + , subconto_3_id int + , amount_dt numeric(15, 2) + , amount_ct numeric(15, 2) + , constraint osv_01_fk foreign key(account_id) references pgconf.account(id) + , constraint osv_02_fk foreign key(subconto_1_id) references pgconf.analytic(id) + , constraint osv_03_fk foreign key(subconto_2_id) references pgconf.analytic(id) + , constraint osv_04_fk foreign key(subconto_3_id) references pgconf.analytic(id) +); + +drop function pgconf.get_osv_slice; + +create or replace function pgconf.get_osv_slice(_account_id int, _subc_1 int, _subc_2 int) +returns table( + ordercol text[] + , account_num text + , subconto_1_id int + , subconto_1 text + , subconto_2_id int + , subconto_2 text + , subconto_3 text + , amount_dt numeric(15, 2) + , amount_ct numeric(15, 2) +) +language plpgsql +as $$ +begin +return query +with recursive account_tree as ( + select id, parent_id, num + from pgconf.account + where (id = _account_id or _account_id is null) + and parent_id is null + union all + select a.id, a.parent_id, a.num + from account_tree t + join pgconf.account a + on a.parent_id = t.id +)/*1*/search depth first by num set account_order +select + t.account_order::text[] + , t.num as account_num + , a1.id as subconto_1_id + , a1.subconto as subconto_1 + , a2.id as subconto_2_id + , a2.subconto as subconto_2 + , a3.subconto as subconto_3 + , o.amount_dt + , o.amount_ct +from account_tree t +left join pgconf.osv o +on o.account_id = t.id +left join pgconf.analytic a1 +on a1.id = o.subconto_1_id +left join pgconf.analytic a2 +on a2.id = o.subconto_2_id +left join pgconf.analytic a3 +on a3.id = o.subconto_3_id +where + /*2*/pgconf.time_machine_now() between '12:00'::time and '15:00'::time + and (_account_id is null and o.subconto_1_id is null and o.subconto_2_id is null and o.subconto_3_id is null) +--where +-- ((_subc_2 is null or o.subconto_2_id = _subc_2) and subconto_2 is not null); +/*1*/order by t.account_order +; +end; +$$; + +create function pgconf.get_tree_of(_account_id int) + returns table(id int, parent_id int, num text, lev int, is_folder bool) + language sql +as $function$ +with recursive acc as ( + select id, parent_id, num, 1 as l, True as is_folder from pgconf.account + where (id = _account_id and _account_id is not null) or (_account_id is null and parent_id is null) + union all + select a2.id, a2.parent_id, a2.num, a1.l + 1 as l + , exists(select from pgconf.account ca where ca.parent_id = a2.id) as is_folder + from acc a1 + join pgconf.account a2 + on a1.id = a2.parent_id +) +select * from acc a +$function$ +; + +create or replace procedure tests.create_test_data() +language plpgsql +as $$ +begin + call tap.fake_table( + '{pgconf, pgconf, pgconf, pgconf}'::text[], + '{account, analytic, osv, transactions}'::text[], + _make_table_empty => true, + _drop_not_null => false, + _drop_collation => false + ); + + insert into pgconf.account(parent_id, num) values(null, '02'); + + insert into pgconf.account(parent_id, num) + select id, '02.01' + from pgconf.account where num = '02'; + + insert into pgconf.account(parent_id, num) values(null, '01'); + + insert into pgconf.account(parent_id, num) + select id, '03.01' + from pgconf.account where num = '01'; + + insert into pgconf.account(parent_id, num) + select id, '01.01.01' + from pgconf.account where num = '03.01'; + + insert into pgconf.account(parent_id, num) + select id, '01.01.02' + from pgconf.account where num = '03.01'; + + insert into pgconf.account(parent_id, num) + select id, '01.01.01.01' + from pgconf.account where num = '01.01.01'; + + insert into pgconf.account(parent_id, num) + select id, '01.01.01.02' + from pgconf.account where num = '01.01.01'; + + insert into pgconf.account(parent_id, num) + select id, '01.01.02.01' + from pgconf.account where num = '01.01.02'; + + insert into pgconf.analytic(subconto) values('Суб_1'), ('Суб_2'), ('Суб_3'), ('Суб_4'), ('Суб_5'), ('Суб_6'), ('Суб_7'); + + insert into pgconf.transactions(account_num, subconto_1, subconto_2, subconto_3, amount_dt, amount_ct) values + ('01.01.01.01', 'Суб_1', 'Суб_2', 'Суб_4', 10, 0) + , ('01.01.01.01', 'Суб_1', 'Суб_2', 'Суб_5', 10, 0) + , ('01.01.01.01', 'Суб_1', 'Суб_3', 'Суб_6', 10, 0) + , ('01.01.01.02', 'Суб_1', 'Суб_3', 'Суб_7', 10, 0) + , ('01.01.02.01', 'Суб_1', 'Суб_2', 'Суб_4', 10, 0) + , ('02.01', 'Суб_1', 'Суб_3', 'Суб_5', 0, 10) + , ('02.01', 'Суб_1', 'Суб_2', 'Суб_4', 0, 10); + + insert into pgconf.osv(account_id, subconto_1_id, subconto_2_id, subconto_3_id, amount_dt, amount_ct) + select a.id, a1.id, a2.id, a3.id, sum(amount_dt), sum(amount_ct) + from pgconf.account a + join pgconf.transactions t + on t.account_num = a.num + left join pgconf.analytic a1 + on a1.subconto = t.subconto_1 + left join pgconf.analytic a2 + on a2.subconto = t.subconto_2 + left join pgconf.analytic a3 + on a3.subconto = t.subconto_3 + group by a.id, grouping sets( + (a.id, a1.id, a2.id, a3.id) + , (a.id, a1.id, a2.id) + , (a.id, a1.id) + , (a.id) + ); + + insert into pgconf.osv(account_id, subconto_1_id, subconto_2_id, subconto_3_id, amount_dt, amount_ct) + select + acc.id + , null, null, null + , agg.amount_dt + , agg.amount_ct + from pgconf.get_tree_of(null) as acc + left join lateral( + select + sum(osv.amount_dt) as amount_dt + , sum(osv.amount_ct) as amount_ct + from pgconf.osv + where osv.account_id in (select id from pgconf.get_tree_of(acc.id) t where not t.is_folder) + and subconto_1_id is null and subconto_2_id is null and subconto_3_id is null + ) as agg on true + where + acc.is_folder; +end; +$$ + +create or replace function tests.test_osv_ordered_in_depth() +returns setof text +language plpgsql +as $$ +begin + -- GIVEN + call tests.create_test_data(); + + -- WHEN + perform tap.drop_prepared_statement('expected'); + perform tap.drop_prepared_statement('returned'); + + prepare expected as + select num::text + from (values ('01', 1), ('03.01', 2), ('01.01.01', 2), ('01.01.01.01', 3), ('01.01.01.02', 4) + , ('01.01.02', 5), ('01.01.02.01', 6) + , ('02', 7), ('02.01', 8)) as t(num, id) + order by id; + + prepare returned as + select account_num from pgconf.get_osv_slice(null, null, null); + + -- THEN + return query + select tap.results_eq( + 'returned', + 'expected', + 'Счета должны быть отсортированы "сначала в глубину"' + ); + + + create table pgconf.slice as + select * from pgconf.get_osv_slice(null, null, null); + + call tests.print_table_as_json('pgconf', 'slice'); + call tests.print_table_as_json('pgconf', 'account'); + + -- WHEN + perform tap.drop_prepared_statement('expected'); + perform tap.drop_prepared_statement('returned'); +end; +$$; + +drop function pgconf.time_machine_now; + +create or replace function pgconf.time_machine_now() +returns time +language sql +as $$ + select now()::time; +$$ + +create or replace function tests.test_osv_in_time() +returns setof text +language plpgsql +as $$ +begin + -- GIVEN + call tests.create_test_data(); + call tap.mock_func('pgconf', 'time_machine_now', '()' + , '15:01'::time); + + create table pgconf.x as select * from pgconf.time_machine_now(); + call tests.print_table_as_json('pgconf', 'x'); + + -- WHEN + perform tap.drop_prepared_statement('returned'); + + prepare returned as + select * from pgconf.get_osv_slice(null, null, null); + + -- THEN + return query + select tap.is_empty( + 'returned', + 'Время не пришло. ОСВ делать нельзя' + ); + + perform tap.drop_prepared_statement('expected'); +end; +$$; + +create or replace function tests.test_osv_not_in_time() +returns setof text +language plpgsql +as $$ +begin + -- GIVEN + call tests.create_test_data(); + call tap.mock_func('pgconf', 'time_machine_now', '()' + , '13:00'::time); + +-- create table pgconf.x as select +-- "returns" +-- , langname +---- , args +-- from +-- tap.tap_funky; +-- call tests.print_table_as_json('pgconf', 'x'); + + -- WHEN + perform tap.drop_prepared_statement('returned'); + + prepare returned as + select * from pgconf.get_osv_slice(null, null, null); + + -- THEN + return query + select tap.isnt_empty( + 'returned', + 'Время пришло. ОСВ делать можно' + ); + + perform tap.drop_prepared_statement('expected'); +end; +$$; + +select * from tap.runtests('tests', '^test_'); + +select * from tap.runtests('tests', 'test_osv_not_in_time'); + +order by account_id, subconto_3_id nulls last, subconto_2_id nulls last, subconto_1_id nulls last \ No newline at end of file From 69aa765f354a15b47d5b6a1f5757ab07c6ad76e7 Mon Sep 17 00:00:00 2001 From: v-maliutin Date: Thu, 2 Jan 2025 23:20:00 +0700 Subject: [PATCH 05/20] mock_func is able to mock plpgsql and sql functionality, + some enhancements to other routines --- examples/mocking_and_faking.sql | 83 ++++++++++---------- sql/pgtap--1.3.3--1.3.4.sql | 131 ++++++++++++++++++++------------ sql/pgtap.sql.in | 130 +++++++++++++++++++------------ 3 files changed, 205 insertions(+), 139 deletions(-) diff --git a/examples/mocking_and_faking.sql b/examples/mocking_and_faking.sql index 74f937f83..17db032d9 100644 --- a/examples/mocking_and_faking.sql +++ b/examples/mocking_and_faking.sql @@ -1,4 +1,10 @@ -create schema pgconf; +create schema if not exists tap; + +CREATE EXTENSION pgtap schema tap; + +create schema if not exists pgconf; + +create schema if not exists tests; set search_path to public, tap; @@ -14,7 +20,10 @@ create table pgconf.account( , constraint account_01_fk foreign key(parent_id) references pgconf.account(id) ); -create table pgconf.analytic(id int generated always as identity primary key, subconto text); +create table pgconf.analytic( + id int generated always as identity primary key + , subconto text +); create table pgconf.transactions( account_num text @@ -38,8 +47,6 @@ create table pgconf.osv( , constraint osv_04_fk foreign key(subconto_3_id) references pgconf.analytic(id) ); -drop function pgconf.get_osv_slice; - create or replace function pgconf.get_osv_slice(_account_id int, _subc_1 int, _subc_2 int) returns table( ordercol text[] @@ -96,7 +103,7 @@ where end; $$; -create function pgconf.get_tree_of(_account_id int) +create or replace function pgconf.get_tree_of(_account_id int) returns table(id int, parent_id int, num text, lev int, is_folder bool) language sql as $function$ @@ -114,13 +121,20 @@ select * from acc a $function$ ; +create or replace function pgconf.time_machine_now() +returns time +language sql +as $$ + select now()::time; +$$; + create or replace procedure tests.create_test_data() language plpgsql as $$ begin call tap.fake_table( - '{pgconf, pgconf, pgconf, pgconf}'::text[], - '{account, analytic, osv, transactions}'::text[], + '{pgconf.account, pgconf.analytic, pgconf.osv, pgconf.transactions}'::text[], + _leave_primary_key => false, _make_table_empty => true, _drop_not_null => false, _drop_collation => false @@ -205,7 +219,7 @@ begin where acc.is_folder; end; -$$ +$$; create or replace function tests.test_osv_ordered_in_depth() returns setof text @@ -214,10 +228,11 @@ as $$ begin -- GIVEN call tests.create_test_data(); + call tap.mock_func('pgconf', 'time_machine_now', '()' + , _return_scalar_value => '13:00'::time); -- WHEN - perform tap.drop_prepared_statement('expected'); - perform tap.drop_prepared_statement('returned'); + perform tap.drop_prepared_statement('{expected, returned}'::text[]); prepare expected as select num::text @@ -241,25 +256,15 @@ begin create table pgconf.slice as select * from pgconf.get_osv_slice(null, null, null); - call tests.print_table_as_json('pgconf', 'slice'); - call tests.print_table_as_json('pgconf', 'account'); + call tap.print_table_as_json('pgconf', 'slice'); + call tap.print_table_as_json('pgconf', 'account'); -- WHEN - perform tap.drop_prepared_statement('expected'); - perform tap.drop_prepared_statement('returned'); + perform tap.drop_prepared_statement('{expected, returned}'::text[]); end; $$; -drop function pgconf.time_machine_now; - -create or replace function pgconf.time_machine_now() -returns time -language sql -as $$ - select now()::time; -$$ - -create or replace function tests.test_osv_in_time() +create or replace function tests.test_osv_on_time() returns setof text language plpgsql as $$ @@ -267,13 +272,13 @@ begin -- GIVEN call tests.create_test_data(); call tap.mock_func('pgconf', 'time_machine_now', '()' - , '15:01'::time); + , _return_scalar_value => '15:01'::time); create table pgconf.x as select * from pgconf.time_machine_now(); - call tests.print_table_as_json('pgconf', 'x'); + call tap.print_table_as_json('pgconf', 'x'); -- WHEN - perform tap.drop_prepared_statement('returned'); + perform tap.drop_prepared_statement('{returned}'::text[]); prepare returned as select * from pgconf.get_osv_slice(null, null, null); @@ -285,11 +290,11 @@ begin 'Время не пришло. ОСВ делать нельзя' ); - perform tap.drop_prepared_statement('expected'); + perform tap.drop_prepared_statement('{returned}'::text[]); end; $$; -create or replace function tests.test_osv_not_in_time() +create or replace function tests.test_osv_not_on_time() returns setof text language plpgsql as $$ @@ -297,18 +302,10 @@ begin -- GIVEN call tests.create_test_data(); call tap.mock_func('pgconf', 'time_machine_now', '()' - , '13:00'::time); - --- create table pgconf.x as select --- "returns" --- , langname ----- , args --- from --- tap.tap_funky; --- call tests.print_table_as_json('pgconf', 'x'); + , _return_set_value => null, _return_scalar_value => '13:00'::time); -- WHEN - perform tap.drop_prepared_statement('returned'); + perform tap.drop_prepared_statement('{returned}'::text[]); prepare returned as select * from pgconf.get_osv_slice(null, null, null); @@ -320,12 +317,14 @@ begin 'Время пришло. ОСВ делать можно' ); - perform tap.drop_prepared_statement('expected'); + perform tap.drop_prepared_statement('{expected}'::text[]); end; $$; + select * from tap.runtests('tests', '^test_'); -select * from tap.runtests('tests', 'test_osv_not_in_time'); +select * from tap.runtests('tests', 'test_osv_ordered_in_depth'); + +order by account_id, subconto_3_id nulls last, subconto_2_id nulls last, subconto_1_id nulls last -order by account_id, subconto_3_id nulls last, subconto_2_id nulls last, subconto_1_id nulls last \ No newline at end of file diff --git a/sql/pgtap--1.3.3--1.3.4.sql b/sql/pgtap--1.3.3--1.3.4.sql index f5ab93b37..85c907595 100644 --- a/sql/pgtap--1.3.3--1.3.4.sql +++ b/sql/pgtap--1.3.3--1.3.4.sql @@ -61,12 +61,13 @@ left join lateral ( --this procedure creates a mock in place of a real function create or replace procedure mock_func( - in _func_schema text - , in _func_name text - , in _func_args text - , in _return_value anyelement + _func_schema text + , _func_name text + , _func_args text + , _return_set_value text default null + , _return_scalar_value anyelement default null ) ---creates mock in place of a real function +--creates a mock in place of a real function LANGUAGE plpgsql AS $procedure$ declare @@ -74,35 +75,57 @@ declare _func_result_type text; _func_qualified_name text; _func_language text; + _returns_set bool; begin - select - "returns" - , langname - into - _func_result_type - , _func_language - from - tap_funky - where - "schema" = _func_schema + select "returns", langname, returns_set + into _func_result_type, _func_language, _returns_set + from tap_funky + where "schema" = _func_schema and "name" = _func_name; + + if _func_language = 'sql' and _returns_set then + _mock_ddl = ' + create or replace function ' || quote_ident(_func_schema) || '.__' || quote_ident(_func_name) || '(_name text) + returns ' || _func_result_type || ' + language plpgsql + AS $function$ + begin + return query execute _query(' || quote_literal(_return_set_value) || '); + end; + $function$;'; + execute _mock_ddl; + end if; - _mock_ddl = ' - create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' - RETURNS ' || _func_result_type || ' - LANGUAGE ' || _func_language || ' - AS $function$ - select ' || quote_nullable(_return_value) || '::' || pg_typeof(_return_value) || '; - $function$;'; - execute _mock_ddl; + if _returns_set then + _mock_ddl = ' + create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' + returns ' || _func_result_type || ' + language ' || _func_language || ' + AS $function$ + select * from ' || quote_ident(_func_schema) || '.__' || quote_ident(_func_name) || + '(' || quote_literal(_return_set_value) || '); + $function$;'; + execute _mock_ddl; + end if; + + if not _returns_set then + _mock_ddl = ' + create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' + RETURNS ' || _func_result_type || ' + LANGUAGE ' || _func_language || ' + AS $function$ + select ' || quote_nullable(_return_scalar_value) || '::' || pg_typeof(_return_scalar_value) || '; + $function$;'; + execute _mock_ddl; + end if; end $procedure$; CREATE OR REPLACE PROCEDURE fake_table( - IN _table_schema text[], - IN _table_name text[], - in _make_table_empty boolean default false, - IN _drop_not_null boolean DEFAULT false, - IN _drop_collation boolean DEFAULT false + _table_ident text[], + _make_table_empty boolean default false, + _leave_primary_key boolean default false, + _drop_not_null boolean DEFAULT false, + _drop_collation boolean DEFAULT false ) --It frees a table from any constraint (we call such a table as a fake) --faked table is a full copy of _table_name, but has no any constraint @@ -117,33 +140,37 @@ declare begin for _table in select - quote_ident(table_schema) table_schema, - quote_ident(table_name) table_name, - table_schema table_schema_l, - table_name table_name_l - from - unnest(_table_schema, _table_name) as t(table_schema, table_name) + quote_ident(coalesce((parse_ident(table_ident))[1], '')) table_schema, + quote_ident(coalesce((parse_ident(table_ident))[2], '')) table_name, + coalesce((parse_ident(table_ident))[1], '') table_schema_l, + coalesce((parse_ident(table_ident))[2], '') table_name_l + from + unnest(_table_ident) as t(table_ident) loop for _fk_table in -- collect all table's relations including primary key and unique constraint select distinct * from ( select - fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord + fk_schema_name table_schema, fk_table_name table_name + , fk_constraint_name constraint_name, false as is_pk, 1 as ord from pg_all_foreign_keys where fk_schema_name = _table.table_schema_l and fk_table_name = _table.table_name_l union all select - fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord + fk_schema_name table_schema, fk_table_name table_name + , fk_constraint_name constraint_name, false as is_pk, 1 as ord from pg_all_foreign_keys where pk_schema_name = _table.table_schema_l and pk_table_name = _table.table_name_l union all select - table_schema, table_name, constraint_name, 2 as ord + table_schema, table_name + , constraint_name + , case when constraint_type = 'PRIMARY KEY' then true else false end as is_pk, 2 as ord from information_schema.table_constraints where @@ -153,9 +180,11 @@ begin ) as t order by ord loop - _fake_ddl = 'alter table ' || _fk_table.table_schema || '.' || _fk_table.table_name || ' - drop constraint ' || _fk_table.constraint_name || ';'; - execute _fake_ddl; + if not(_leave_primary_key and _fk_table.is_pk) then + _fake_ddl = 'alter table ' || _fk_table.table_schema || '.' || _fk_table.table_name || ' + drop constraint ' || _fk_table.constraint_name || ';'; + execute _fake_ddl; + end if; end loop; if _make_table_empty then @@ -392,19 +421,25 @@ begin end $$ LANGUAGE plpgsql; -create or replace function drop_prepared_statement(_statement_name text) -returns bool as $$ +create or replace function drop_prepared_statement(_statements text[]) +returns setof bool as $$ +declare + _statement record; begin - if exists(select * from pg_prepared_statements where "name" = _statement_name) then - EXECUTE format('deallocate %I;', _statement_name); - return true; - end if; - return false; + for _statement in select _name from unnest(_statements) as t(_name) loop + if exists(select * from pg_prepared_statements where "name" = _statement._name) then + EXECUTE format('deallocate %I;', _statement._name); + return next true; + else + return next false; + end if; + end loop; end $$ language plpgsql; -create or replace procedure print_table_as_json(in _table_schema text, in _table_name text) + +create or replace procedure print_table_as_json(_table_schema text, _table_name text) language plpgsql AS $procedure$ declare diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index ed94e3728..c3dd18462 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -11492,12 +11492,13 @@ left join lateral ( --this procedure creates a mock in place of a real function create or replace procedure mock_func( - in _func_schema text - , in _func_name text - , in _func_args text - , in _return_value anyelement + _func_schema text + , _func_name text + , _func_args text + , _return_set_value text default null + , _return_scalar_value anyelement default null ) ---creates mock in place of a real function +--creates a mock in place of a real function LANGUAGE plpgsql AS $procedure$ declare @@ -11505,35 +11506,57 @@ declare _func_result_type text; _func_qualified_name text; _func_language text; + _returns_set bool; begin - select - "returns" - , langname - into - _func_result_type - , _func_language - from - tap_funky - where - "schema" = _func_schema + select "returns", langname, returns_set + into _func_result_type, _func_language, _returns_set + from tap_funky + where "schema" = _func_schema and "name" = _func_name; + + if _func_language = 'sql' and _returns_set then + _mock_ddl = ' + create or replace function ' || quote_ident(_func_schema) || '.__' || quote_ident(_func_name) || '(_name text) + returns ' || _func_result_type || ' + language plpgsql + AS $function$ + begin + return query execute _query(' || quote_literal(_return_set_value) || '); + end; + $function$;'; + execute _mock_ddl; + end if; - _mock_ddl = ' - create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' - RETURNS ' || _func_result_type || ' - LANGUAGE ' || _func_language || ' - AS $function$ - select ' || quote_nullable(_return_value) || '::' || pg_typeof(_return_value) || '; - $function$;'; - execute _mock_ddl; + if _returns_set then + _mock_ddl = ' + create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' + returns ' || _func_result_type || ' + language ' || _func_language || ' + AS $function$ + select * from ' || quote_ident(_func_schema) || '.__' || quote_ident(_func_name) || + '(' || quote_literal(_return_set_value) || '); + $function$;'; + execute _mock_ddl; + end if; + + if not _returns_set then + _mock_ddl = ' + create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' + RETURNS ' || _func_result_type || ' + LANGUAGE ' || _func_language || ' + AS $function$ + select ' || quote_nullable(_return_scalar_value) || '::' || pg_typeof(_return_scalar_value) || '; + $function$;'; + execute _mock_ddl; + end if; end $procedure$; CREATE OR REPLACE PROCEDURE fake_table( - IN _table_schema text[], - IN _table_name text[], - IN _make_table_empty boolean default false, - IN _drop_not_null boolean DEFAULT false, - IN _drop_collation boolean DEFAULT false + _table_ident text[], + _make_table_empty boolean default false, + _leave_primary_key boolean default false, + _drop_not_null boolean DEFAULT false, + _drop_collation boolean DEFAULT false ) --It frees a table from any constraint (we call such a table as a fake) --faked table is a full copy of _table_name, but has no any constraint @@ -11548,33 +11571,37 @@ declare begin for _table in select - quote_ident(table_schema) table_schema, - quote_ident(table_name) table_name, - table_schema table_schema_l, - table_name table_name_l - from - unnest(_table_schema, _table_name) as t(table_schema, table_name) + quote_ident(coalesce((parse_ident(table_ident))[1], '')) table_schema, + quote_ident(coalesce((parse_ident(table_ident))[2], '')) table_name, + coalesce((parse_ident(table_ident))[1], '') table_schema_l, + coalesce((parse_ident(table_ident))[2], '') table_name_l + from + unnest(_table_ident) as t(table_ident) loop for _fk_table in -- collect all table's relations including primary key and unique constraint select distinct * from ( select - fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord + fk_schema_name table_schema, fk_table_name table_name + , fk_constraint_name constraint_name, false as is_pk, 1 as ord from pg_all_foreign_keys where fk_schema_name = _table.table_schema_l and fk_table_name = _table.table_name_l union all select - fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord + fk_schema_name table_schema, fk_table_name table_name + , fk_constraint_name constraint_name, false as is_pk, 1 as ord from pg_all_foreign_keys where pk_schema_name = _table.table_schema_l and pk_table_name = _table.table_name_l union all select - table_schema, table_name, constraint_name, 2 as ord + table_schema, table_name + , constraint_name + , case when constraint_type = 'PRIMARY KEY' then true else false end as is_pk, 2 as ord from information_schema.table_constraints where @@ -11584,12 +11611,13 @@ begin ) as t order by ord loop - _fake_ddl = 'alter table ' || _fk_table.table_schema || '.' || _fk_table.table_name || ' - drop constraint ' || _fk_table.constraint_name || ';'; - execute _fake_ddl; + if not(_leave_primary_key and _fk_table.is_pk) then + _fake_ddl = 'alter table ' || _fk_table.table_schema || '.' || _fk_table.table_name || ' + drop constraint ' || _fk_table.constraint_name || ';'; + execute _fake_ddl; + end if; end loop; - --make table empty if _make_table_empty then _fake_ddl = 'truncate table ' || _table.table_schema || '.' || _table.table_name || ';'; execute _fake_ddl; @@ -11826,15 +11854,19 @@ begin end $$ LANGUAGE plpgsql; -create or replace function drop_prepared_statement(_statement_name text) ---It drops a prepared statement to be sure you can run a test many times -returns bool as $$ +create or replace function drop_prepared_statement(_statements text[]) +returns setof bool as $$ +declare + _statement record; begin - if exists(select * from pg_prepared_statements where "name" = _statement_name) then - EXECUTE format('deallocate %I;', _statement_name); - return true; - end if; - return false; + for _statement in select _name from unnest(_statements) as t(_name) loop + if exists(select * from pg_prepared_statements where "name" = _statement._name) then + EXECUTE format('deallocate %I;', _statement._name); + return next true; + else + return next false; + end if; + end loop; end $$ language plpgsql; From 0f8c32d815053eb1e143f6ffc15706ebda259fde Mon Sep 17 00:00:00 2001 From: v-maliutin Date: Sat, 4 Jan 2025 16:54:28 +0700 Subject: [PATCH 06/20] fixed tap_funky and implemented call_count --- examples/mocking_and_faking.sql | 103 +++++++++++++++++----------- sql/pgtap--1.3.3--1.3.4.sql | 105 ++++++++++++++--------------- sql/pgtap--unpackaged--0.91.0.sql | 5 +- sql/pgtap.sql.in | 108 +++++++++++++++--------------- 4 files changed, 172 insertions(+), 149 deletions(-) diff --git a/examples/mocking_and_faking.sql b/examples/mocking_and_faking.sql index 17db032d9..96bc00d08 100644 --- a/examples/mocking_and_faking.sql +++ b/examples/mocking_and_faking.sql @@ -1,12 +1,14 @@ create schema if not exists tap; +DROP EXTENSION pgtap; + CREATE EXTENSION pgtap schema tap; create schema if not exists pgconf; create schema if not exists tests; -set search_path to public, tap; +set search_path to public, tap, pgconf; drop table pgconf.osv; drop table pgconf.transactions; @@ -128,6 +130,48 @@ as $$ select now()::time; $$; +create or replace procedure pgconf.make_osv_report() +language plpgsql +as $$ +begin + insert into pgconf.osv(account_id, subconto_1_id, subconto_2_id, subconto_3_id, amount_dt, amount_ct) + select a.id, a1.id, a2.id, a3.id, sum(amount_dt), sum(amount_ct) + from pgconf.account a + join pgconf.transactions t + on t.account_num = a.num + left join pgconf.analytic a1 + on a1.subconto = t.subconto_1 + left join pgconf.analytic a2 + on a2.subconto = t.subconto_2 + left join pgconf.analytic a3 + on a3.subconto = t.subconto_3 + group by a.id, grouping sets( + (a.id, a1.id, a2.id, a3.id) + , (a.id, a1.id, a2.id) + , (a.id, a1.id) + , (a.id) + ); + + insert into pgconf.osv(account_id, subconto_1_id, subconto_2_id, subconto_3_id, amount_dt, amount_ct) + select + acc.id + , null, null, null + , agg.amount_dt + , agg.amount_ct + from pgconf.get_tree_of(null) as acc + left join lateral( + select + sum(osv.amount_dt) as amount_dt + , sum(osv.amount_ct) as amount_ct + from pgconf.osv + where osv.account_id in (select id from pgconf.get_tree_of(acc.id) t where not t.is_folder) + and subconto_1_id is null and subconto_2_id is null and subconto_3_id is null + ) as agg on true + where + acc.is_folder; +end; +$$; + create or replace procedure tests.create_test_data() language plpgsql as $$ @@ -183,41 +227,7 @@ begin , ('02.01', 'Суб_1', 'Суб_3', 'Суб_5', 0, 10) , ('02.01', 'Суб_1', 'Суб_2', 'Суб_4', 0, 10); - insert into pgconf.osv(account_id, subconto_1_id, subconto_2_id, subconto_3_id, amount_dt, amount_ct) - select a.id, a1.id, a2.id, a3.id, sum(amount_dt), sum(amount_ct) - from pgconf.account a - join pgconf.transactions t - on t.account_num = a.num - left join pgconf.analytic a1 - on a1.subconto = t.subconto_1 - left join pgconf.analytic a2 - on a2.subconto = t.subconto_2 - left join pgconf.analytic a3 - on a3.subconto = t.subconto_3 - group by a.id, grouping sets( - (a.id, a1.id, a2.id, a3.id) - , (a.id, a1.id, a2.id) - , (a.id, a1.id) - , (a.id) - ); - - insert into pgconf.osv(account_id, subconto_1_id, subconto_2_id, subconto_3_id, amount_dt, amount_ct) - select - acc.id - , null, null, null - , agg.amount_dt - , agg.amount_ct - from pgconf.get_tree_of(null) as acc - left join lateral( - select - sum(osv.amount_dt) as amount_dt - , sum(osv.amount_ct) as amount_ct - from pgconf.osv - where osv.account_id in (select id from pgconf.get_tree_of(acc.id) t where not t.is_folder) - and subconto_1_id is null and subconto_2_id is null and subconto_3_id is null - ) as agg on true - where - acc.is_folder; + call pgconf.make_osv_report(); end; $$; @@ -321,10 +331,25 @@ begin end; $$; +create or replace function tests.test_get_tree_of_called_times() +returns setof text +language plpgsql +as $$ +begin + -- GIVEN + call tests.create_test_data(); + + -- THEN + return query + select tap.call_count(6, 'pgconf' + , 'get_tree_of', '{int}'::name[]); +end; +$$; + +set track_functions = 'all' ; select * from tap.runtests('tests', '^test_'); -select * from tap.runtests('tests', 'test_osv_ordered_in_depth'); - -order by account_id, subconto_3_id nulls last, subconto_2_id nulls last, subconto_1_id nulls last +select * from tap.runtests('tests', 'test_get_tree_of_called_times'); +order by account_id, subconto_3_id nulls last, subconto_2_id nulls last, subconto_1_id nulls last \ No newline at end of file diff --git a/sql/pgtap--1.3.3--1.3.4.sql b/sql/pgtap--1.3.3--1.3.4.sql index 85c907595..3182b0fcf 100644 --- a/sql/pgtap--1.3.3--1.3.4.sql +++ b/sql/pgtap--1.3.3--1.3.4.sql @@ -16,48 +16,27 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; ---added a three columns: "args", "returns", "langname" used in mock_func function +--added a column "langname" used in mock_func function CREATE OR REPLACE VIEW tap_funky -AS -SELECT p.oid, - n.nspname AS schema, - p.proname AS name, - pg_get_userbyid(p.proowner) AS owner, - arg._types as args, - proc_return."returns", - p.prolang AS langoid, - p.proisstrict AS is_strict, - tap._prokind(p.oid) AS kind, - p.prosecdef AS is_definer, - p.proretset AS returns_set, - p.provolatile::character(1) AS volatility, - pg_function_is_visible(p.oid) AS is_visible, - l.lanname AS langname -FROM - pg_proc p -JOIN - pg_namespace n -ON - p.pronamespace = n.oid -LEFT JOIN - pg_language l -ON - l.oid = p.prolang -left join lateral ( - select string_agg(nullif(_type, '')::regtype::text, ', ') as _types - from unnest(regexp_split_to_array(p.proargtypes::text, ' ')) as _type -) as arg on true - LEFT JOIN LATERAL ( - SELECT (n.nspname::text || '.'::text) || p.proname::text AS qualified -) proc_name ON true -left join lateral ( - select - case - when n.nspname != 'pg_catalog' - then pg_get_function_result((concat(proc_name.qualified, '(', arg._types, ')')::regprocedure)::oid) - else null - end AS "returns" -) as proc_return on true; + AS SELECT p.oid AS oid, + n.nspname AS schema, + p.proname AS name, + pg_catalog.pg_get_userbyid(p.proowner) AS owner, + array_to_string(p.proargtypes::regtype[], ',') AS args, + CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END + || p.prorettype::regtype AS returns, + p.prolang AS langoid, + p.proisstrict AS is_strict, + _prokind(p.oid) AS kind, + p.prosecdef AS is_definer, + p.proretset AS returns_set, + p.provolatile::char AS volatility, + pg_catalog.pg_function_is_visible(p.oid) AS is_visible, + l.lanname AS langname + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid + LEFT JOIN pg_language l ON l.oid = p.prolang +; --this procedure creates a mock in place of a real function create or replace procedure mock_func( @@ -399,24 +378,31 @@ begin end $$ LANGUAGE plpgsql; -create or replace function called_times(_call_count int, _proc_schema text, _proc_name text) +create or replace function call_count( + _call_count int + , _func_schema name + , _func_name name + , _func_args name[]) returns setof text as $$ declare _actual_call_count int; + _track_functions_setting text; begin - select - call_cnt - into - _actual_call_count - from - call_count - where - routine_schema = _proc_schema - and routine_name = _proc_name; - + select current_setting('track_functions') into _track_functions_setting; + + if _track_functions_setting != 'all' then + return query select fail('track_functions setting is not set. Must be all'); + return; + end if; + + select calls into _actual_call_count + from pg_stat_xact_user_functions + where funcid = _get_func_oid(_func_schema, _func_name, _func_args); + return query select ok( _actual_call_count = _call_count - , format('routine %L.%L must have been called %L times', _proc_schema, _proc_name, _call_count) + , format('routine %I.%I must have been called %L times, actual call count is %L' + , _func_schema, _func_name, _call_count, _actual_call_count) ); end $$ LANGUAGE plpgsql; @@ -468,3 +454,16 @@ begin _json = $$select * from /*$$ || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || $$*/ json_to_recordset('$$ || _json || $$') as t($$ || _columns || $$)$$; raise notice '%', _json; end $procedure$; + +CREATE OR REPLACE FUNCTION _get_func_oid(name, name, name[]) + RETURNS oid + LANGUAGE sql +AS $function$ + SELECT oid + FROM tap_funky + WHERE "schema" = $1 + and "name" = $2 + AND args = _funkargs($3) + AND is_visible +$function$ +; diff --git a/sql/pgtap--unpackaged--0.91.0.sql b/sql/pgtap--unpackaged--0.91.0.sql index cec4c09a2..6dbca3806 100644 --- a/sql/pgtap--unpackaged--0.91.0.sql +++ b/sql/pgtap--unpackaged--0.91.0.sql @@ -717,6 +717,7 @@ ALTER EXTENSION pgtap ADD PROCEDURE mock_func( TEXT, TEXT, TEXT, ANYELEMENT ); ALTER EXTENSION pgtap ADD PROCEDURE fake_table( TEXT[], TEXT[], BOOL, BOOL ); ALTER EXTENSION pgtap ADD PROCEDURE willing_count_calls_of( TEXT, TEXT, TEXT ); ALTER EXTENSION pgtap ADD FUNCTION called_once( TEXT , TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION called_times( INT, TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION call_count( INT, TEXT, TEXT ); ALTER EXTENSION pgtap ADD FUNCTION drop_prepared_statement( TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION print_table_as_json( TEXT, TEXT ); \ No newline at end of file +ALTER EXTENSION pgtap ADD FUNCTION print_table_as_json( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD FUNCTION _get_func_oid(name, name[]); \ No newline at end of file diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index c3dd18462..9ede52022 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -11447,48 +11447,27 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; ---added a three columns: "args", "returns", "langname" used in mock_func function +--added a column "langname" used in mock_func function CREATE OR REPLACE VIEW tap_funky -AS -SELECT p.oid, - n.nspname AS schema, - p.proname AS name, - pg_get_userbyid(p.proowner) AS owner, - arg._types as args, - proc_return."returns", - p.prolang AS langoid, - p.proisstrict AS is_strict, - tap._prokind(p.oid) AS kind, - p.prosecdef AS is_definer, - p.proretset AS returns_set, - p.provolatile::character(1) AS volatility, - pg_function_is_visible(p.oid) AS is_visible, - l.lanname AS langname -FROM - pg_proc p -JOIN - pg_namespace n -ON - p.pronamespace = n.oid -LEFT JOIN - pg_language l -ON - l.oid = p.prolang -left join lateral ( - select string_agg(nullif(_type, '')::regtype::text, ', ') as _types - from unnest(regexp_split_to_array(p.proargtypes::text, ' ')) as _type -) as arg on true - LEFT JOIN LATERAL ( - SELECT (n.nspname::text || '.'::text) || p.proname::text AS qualified -) proc_name ON true -left join lateral ( - select - case - when n.nspname != 'pg_catalog' - then pg_get_function_result((concat(proc_name.qualified, '(', arg._types, ')')::regprocedure)::oid) - else null - end AS "returns" -) as proc_return on true; + AS SELECT p.oid AS oid, + n.nspname AS schema, + p.proname AS name, + pg_catalog.pg_get_userbyid(p.proowner) AS owner, + array_to_string(p.proargtypes::regtype[], ',') AS args, + CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END + || p.prorettype::regtype AS returns, + p.prolang AS langoid, + p.proisstrict AS is_strict, + _prokind(p.oid) AS kind, + p.prosecdef AS is_definer, + p.proretset AS returns_set, + p.provolatile::char AS volatility, + pg_catalog.pg_function_is_visible(p.oid) AS is_visible, + l.lanname AS langname + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid + LEFT JOIN pg_language l ON l.oid = p.prolang +; --this procedure creates a mock in place of a real function create or replace procedure mock_func( @@ -11831,25 +11810,31 @@ begin end $$ LANGUAGE plpgsql; -create or replace function called_times(_call_count int, _proc_schema text, _proc_name text) ---Insures that a routine have been called specified times +create or replace function call_count( + _call_count int + , _func_schema name + , _func_name name + , _func_args name[]) returns setof text as $$ declare _actual_call_count int; + _track_functions_setting text; begin - select - call_cnt - into - _actual_call_count - from - call_count - where - routine_schema = _proc_schema - and routine_name = _proc_name; - + select current_setting('track_functions') into _track_functions_setting; + + if _track_functions_setting != 'all' then + return query select fail('track_functions setting is not set. Must be all'); + return; + end if; + + select calls into _actual_call_count + from pg_stat_xact_user_functions + where funcid = _get_func_oid(_func_schema, _func_name, _func_args); + return query select ok( _actual_call_count = _call_count - , format('routine %L.%L must have been called %L times', _proc_schema, _proc_name, _call_count) + , format('routine %I.%I must have been called %L times, actual call count is %L' + , _func_schema, _func_name, _call_count, _actual_call_count) ); end $$ LANGUAGE plpgsql; @@ -11899,4 +11884,17 @@ begin _json = $$select * from /*$$ || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || $$*/ json_to_recordset('$$ || _json || $$') as t($$ || _columns || $$)$$; raise notice '%', _json; -end $procedure$; \ No newline at end of file +end $procedure$; + +CREATE OR REPLACE FUNCTION _get_func_oid(name, name, name[]) + RETURNS oid + LANGUAGE sql +AS $function$ + SELECT oid + FROM tap_funky + WHERE "schema" = $1 + and "name" = $2 + AND args = _funkargs($3) + AND is_visible +$function$ +; From c733367ceebc230edb45f77d6351f131250b4c57 Mon Sep 17 00:00:00 2001 From: v-maliutin Date: Mon, 6 Jan 2025 22:43:19 +0700 Subject: [PATCH 07/20] fixed returns column in tap_funky and added a regression test --- examples/mocking_and_faking.sql | 6 +- sql/pgtap--1.3.3--1.3.4.sql | 244 +++++++----------------------- sql/pgtap--unpackaged--0.91.0.sql | 2 - sql/pgtap.sql.in | 244 +++++++----------------------- test/expected/funcmock.out | 5 + test/sql/funcmock.sql | 81 ++++++++++ 6 files changed, 197 insertions(+), 385 deletions(-) create mode 100644 test/expected/funcmock.out create mode 100644 test/sql/funcmock.sql diff --git a/examples/mocking_and_faking.sql b/examples/mocking_and_faking.sql index 96bc00d08..63dd64b9c 100644 --- a/examples/mocking_and_faking.sql +++ b/examples/mocking_and_faking.sql @@ -348,8 +348,10 @@ $$; set track_functions = 'all' ; +set search_path to public, tap, pgconf; + select * from tap.runtests('tests', '^test_'); -select * from tap.runtests('tests', 'test_get_tree_of_called_times'); +select * from tap.runtests('tests', 'test_osv_on_time'); -order by account_id, subconto_3_id nulls last, subconto_2_id nulls last, subconto_1_id nulls last \ No newline at end of file +order by account_id, subconto_3_id nulls last, subconto_2_id nulls last, subconto_1_id nulls last diff --git a/sql/pgtap--1.3.3--1.3.4.sql b/sql/pgtap--1.3.3--1.3.4.sql index 3182b0fcf..8bd3df905 100644 --- a/sql/pgtap--1.3.3--1.3.4.sql +++ b/sql/pgtap--1.3.3--1.3.4.sql @@ -44,7 +44,7 @@ create or replace procedure mock_func( , _func_name text , _func_args text , _return_set_value text default null - , _return_scalar_value anyelement default null + , _return_scalar_value anyelement default null::text ) --creates a mock in place of a real function LANGUAGE plpgsql @@ -71,12 +71,9 @@ begin begin return query execute _query(' || quote_literal(_return_set_value) || '); end; - $function$;'; + $function$;'; execute _mock_ddl; - end if; - - if _returns_set then - _mock_ddl = ' + _mock_ddl = ' create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' returns ' || _func_result_type || ' language ' || _func_language || ' @@ -86,6 +83,19 @@ begin $function$;'; execute _mock_ddl; end if; + + if _func_language = 'plpgsql' and _returns_set then + _mock_ddl = ' + create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' + returns ' || _func_result_type || ' + language plpgsql + AS $function$ + begin + return query execute _query(' || quote_literal(_return_set_value) || '); + end; + $function$;'; + execute _mock_ddl; + end if; if not _returns_set then _mock_ddl = ' @@ -99,6 +109,7 @@ begin end if; end $procedure$; + CREATE OR REPLACE PROCEDURE fake_table( _table_ident text[], _make_table_empty boolean default false, @@ -194,190 +205,6 @@ begin end loop; end $procedure$; -CREATE OR REPLACE FUNCTION _runner(text[], text[], text[], text[], text[]) - RETURNS SETOF text - LANGUAGE plpgsql -AS $function$ -DECLARE - startup ALIAS FOR $1; - shutdown ALIAS FOR $2; - setup ALIAS FOR $3; - teardown ALIAS FOR $4; - tests ALIAS FOR $5; - tap TEXT; - tfaild INTEGER := 0; - ffaild INTEGER := 0; - tnumb INTEGER := 0; - fnumb INTEGER := 0; - tok BOOLEAN := TRUE; -BEGIN - BEGIN - -- No plan support. - PERFORM * FROM no_plan(); - FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; - EXCEPTION - -- Catch all exceptions and simply rethrow custom exceptions. This - -- will roll back everything in the above block. - WHEN raise_exception THEN RAISE EXCEPTION '%', SQLERRM; - END; - - -- Record how startup tests have failed. - tfaild := num_failed(); - - FOR i IN 1..COALESCE(array_upper(tests, 1), 0) LOOP - - -- What subtest are we running? - RETURN NEXT diag_test_name('Subtest: ' || tests[i]); - - -- Reset the results. - tok := TRUE; - tnumb := COALESCE(_get('curr_test'), 0); - - IF tnumb > 0 THEN - EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; - PERFORM _set('curr_test', 0); - PERFORM _set('failed', 0); - END IF; - - DECLARE - errstate text; - errmsg text; - detail text; - hint text; - context text; - schname text; - tabname text; - colname text; - chkname text; - typname text; - BEGIN - BEGIN - -- Run the setup functions. - FOR tap IN SELECT * FROM _runem(setup, false) LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Create a temp table to collect call count - execute 'create temp table call_count( - routine_schema text not null - , routine_name text not null - , call_cnt int - ) ON COMMIT DROP;'; - - -- Run the actual test function. - FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Run the teardown functions. - FOR tap IN SELECT * FROM _runem(teardown, false) LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Emit the plan. - fnumb := COALESCE(_get('curr_test'), 0); - RETURN NEXT ' 1..' || fnumb; - - -- Emit any error messages. - IF fnumb = 0 THEN - RETURN NEXT ' # No tests run!'; - tok = false; - ELSE - -- Report failures. - ffaild := num_failed(); - IF ffaild > 0 THEN - tok := FALSE; - RETURN NEXT ' ' || diag( - 'Looks like you failed ' || ffaild || ' test' || - CASE ffaild WHEN 1 THEN '' ELSE 's' END - || ' of ' || fnumb - ); - END IF; - END IF; - - EXCEPTION WHEN OTHERS THEN - -- Something went wrong. Record that fact. - errstate := SQLSTATE; - errmsg := SQLERRM; - GET STACKED DIAGNOSTICS - detail = PG_EXCEPTION_DETAIL, - hint = PG_EXCEPTION_HINT, - context = PG_EXCEPTION_CONTEXT, - schname = SCHEMA_NAME, - tabname = TABLE_NAME, - colname = COLUMN_NAME, - chkname = CONSTRAINT_NAME, - typname = PG_DATATYPE_NAME; - END; - - -- Always raise an exception to rollback any changes. - RAISE EXCEPTION '__TAP_ROLLBACK__'; - - EXCEPTION WHEN raise_exception THEN - IF errmsg IS NOT NULL THEN - -- Something went wrong. Emit the error message. - tok := FALSE; - RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( - errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname - )), '^', ' ', 'gn'); - errmsg := NULL; - END IF; - END; - - -- Restore the sequence. - EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; - PERFORM _set('curr_test', tnumb); - PERFORM _set('failed', tfaild); - - -- Record this test. - RETURN NEXT ok(tok, tests[i]); - IF NOT tok THEN tfaild := tfaild + 1; END IF; - - END LOOP; - - -- Run the shutdown functions. - FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; - - -- Finish up. - FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP - RETURN NEXT tap; - END LOOP; - - -- Clean up and return. - PERFORM _cleanup(); - RETURN; -END; -$function$; - -create or replace procedure willing_count_calls_of(_proc_schema text, _proc_name text, _proc_args text) - LANGUAGE plpgsql -AS $procedure$ -declare - _ddl text; -begin - insert into call_count(routine_schema, routine_name, call_cnt) values(_proc_schema, _proc_name, 0); - _ddl = ' - create or replace procedure ' || quote_ident(_proc_schema) || '.' || quote_ident(_proc_name) || _proc_args || ' - LANGUAGE plpgsql - AS $proc$ - begin - update call_count set call_cnt = call_cnt + 1 - where routine_schema = ' || quote_literal(_proc_schema) || ' - and routine_name = ' || quote_literal(_proc_name) || '; - end - $proc$;'; - raise notice '%', _ddl; - execute _ddl; -end -$procedure$; - -create or replace function called_once(_proc_schema text, _proc_name text) -returns setof text as $$ -begin - return query select called_times(1, _proc_schema, _proc_name); -end $$ -LANGUAGE plpgsql; - create or replace function call_count( _call_count int , _func_schema name @@ -467,3 +294,40 @@ AS $function$ AND is_visible $function$ ; + +CREATE OR REPLACE VIEW tap_funky + AS + SELECT + p.oid AS oid, + n.nspname AS schema, + p.proname AS name, + pg_catalog.pg_get_userbyid(p.proowner) AS owner, + proc_name.args AS args, + lower(coalesce( + proc_return."returns", + proc_return.sys_returns)) AS "returns", + p.prolang AS langoid, + p.proisstrict AS is_strict, + _prokind(p.oid) AS kind, + p.prosecdef AS is_definer, + p.proretset AS returns_set, + p.provolatile::char AS volatility, + pg_catalog.pg_function_is_visible(p.oid) AS is_visible, + l.lanname AS langname +FROM pg_catalog.pg_proc p +JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid +LEFT JOIN pg_language l ON l.oid = p.prolang +LEFT JOIN LATERAL ( + SELECT + (n.nspname::text || '.'::text) || p.proname::text AS qualified, + array_to_string(p.proargtypes::regtype[], ',') AS args +) proc_name ON true +LEFT JOIN LATERAL ( + SELECT + CASE + WHEN n.nspname != 'pg_catalog' + THEN pg_get_function_result((concat(proc_name.qualified, '(', proc_name.args, ')')::regprocedure)::oid) + ELSE NULL + END AS "returns", + CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END || p.prorettype::regtype AS sys_returns +) AS proc_return ON TRUE; diff --git a/sql/pgtap--unpackaged--0.91.0.sql b/sql/pgtap--unpackaged--0.91.0.sql index 6dbca3806..9a9bcf57d 100644 --- a/sql/pgtap--unpackaged--0.91.0.sql +++ b/sql/pgtap--unpackaged--0.91.0.sql @@ -715,8 +715,6 @@ ALTER EXTENSION pgtap ADD FUNCTION db_owner_is ( NAME, NAME ); ALTER EXTENSION pgtap ADD PROCEDURE mock_func( TEXT, TEXT, TEXT, ANYELEMENT ); ALTER EXTENSION pgtap ADD PROCEDURE fake_table( TEXT[], TEXT[], BOOL, BOOL ); -ALTER EXTENSION pgtap ADD PROCEDURE willing_count_calls_of( TEXT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION called_once( TEXT , TEXT ); ALTER EXTENSION pgtap ADD FUNCTION call_count( INT, TEXT, TEXT ); ALTER EXTENSION pgtap ADD FUNCTION drop_prepared_statement( TEXT ); ALTER EXTENSION pgtap ADD FUNCTION print_table_as_json( TEXT, TEXT ); diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 9ede52022..524d58086 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -11475,7 +11475,7 @@ create or replace procedure mock_func( , _func_name text , _func_args text , _return_set_value text default null - , _return_scalar_value anyelement default null + , _return_scalar_value anyelement default null::text ) --creates a mock in place of a real function LANGUAGE plpgsql @@ -11502,12 +11502,9 @@ begin begin return query execute _query(' || quote_literal(_return_set_value) || '); end; - $function$;'; + $function$;'; execute _mock_ddl; - end if; - - if _returns_set then - _mock_ddl = ' + _mock_ddl = ' create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' returns ' || _func_result_type || ' language ' || _func_language || ' @@ -11517,6 +11514,19 @@ begin $function$;'; execute _mock_ddl; end if; + + if _func_language = 'plpgsql' and _returns_set then + _mock_ddl = ' + create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' + returns ' || _func_result_type || ' + language plpgsql + AS $function$ + begin + return query execute _query(' || quote_literal(_return_set_value) || '); + end; + $function$;'; + execute _mock_ddl; + end if; if not _returns_set then _mock_ddl = ' @@ -11625,191 +11635,6 @@ begin end loop; end $procedure$; -CREATE OR REPLACE FUNCTION _runner(text[], text[], text[], text[], text[]) - RETURNS SETOF text - LANGUAGE plpgsql -AS $function$ -DECLARE - startup ALIAS FOR $1; - shutdown ALIAS FOR $2; - setup ALIAS FOR $3; - teardown ALIAS FOR $4; - tests ALIAS FOR $5; - tap TEXT; - tfaild INTEGER := 0; - ffaild INTEGER := 0; - tnumb INTEGER := 0; - fnumb INTEGER := 0; - tok BOOLEAN := TRUE; -BEGIN - BEGIN - -- No plan support. - PERFORM * FROM no_plan(); - FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; - EXCEPTION - -- Catch all exceptions and simply rethrow custom exceptions. This - -- will roll back everything in the above block. - WHEN raise_exception THEN RAISE EXCEPTION '%', SQLERRM; - END; - - -- Record how startup tests have failed. - tfaild := num_failed(); - - FOR i IN 1..COALESCE(array_upper(tests, 1), 0) LOOP - - -- What subtest are we running? - RETURN NEXT diag_test_name('Subtest: ' || tests[i]); - - -- Reset the results. - tok := TRUE; - tnumb := COALESCE(_get('curr_test'), 0); - - IF tnumb > 0 THEN - EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; - PERFORM _set('curr_test', 0); - PERFORM _set('failed', 0); - END IF; - - DECLARE - errstate text; - errmsg text; - detail text; - hint text; - context text; - schname text; - tabname text; - colname text; - chkname text; - typname text; - BEGIN - BEGIN - -- Run the setup functions. - FOR tap IN SELECT * FROM _runem(setup, false) LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Create a temp table to collect call count - execute 'create temp table call_count( - routine_schema text not null - , routine_name text not null - , call_cnt int - ) ON COMMIT DROP;'; - - -- Run the actual test function. - FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Run the teardown functions. - FOR tap IN SELECT * FROM _runem(teardown, false) LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Emit the plan. - fnumb := COALESCE(_get('curr_test'), 0); - RETURN NEXT ' 1..' || fnumb; - - -- Emit any error messages. - IF fnumb = 0 THEN - RETURN NEXT ' # No tests run!'; - tok = false; - ELSE - -- Report failures. - ffaild := num_failed(); - IF ffaild > 0 THEN - tok := FALSE; - RETURN NEXT ' ' || diag( - 'Looks like you failed ' || ffaild || ' test' || - CASE ffaild WHEN 1 THEN '' ELSE 's' END - || ' of ' || fnumb - ); - END IF; - END IF; - - EXCEPTION WHEN OTHERS THEN - -- Something went wrong. Record that fact. - errstate := SQLSTATE; - errmsg := SQLERRM; - GET STACKED DIAGNOSTICS - detail = PG_EXCEPTION_DETAIL, - hint = PG_EXCEPTION_HINT, - context = PG_EXCEPTION_CONTEXT, - schname = SCHEMA_NAME, - tabname = TABLE_NAME, - colname = COLUMN_NAME, - chkname = CONSTRAINT_NAME, - typname = PG_DATATYPE_NAME; - END; - - -- Always raise an exception to rollback any changes. - RAISE EXCEPTION '__TAP_ROLLBACK__'; - - EXCEPTION WHEN raise_exception THEN - IF errmsg IS NOT NULL THEN - -- Something went wrong. Emit the error message. - tok := FALSE; - RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( - errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname - )), '^', ' ', 'gn'); - errmsg := NULL; - END IF; - END; - - -- Restore the sequence. - EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; - PERFORM _set('curr_test', tnumb); - PERFORM _set('failed', tfaild); - - -- Record this test. - RETURN NEXT ok(tok, tests[i]); - IF NOT tok THEN tfaild := tfaild + 1; END IF; - - END LOOP; - - -- Run the shutdown functions. - FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; - - -- Finish up. - FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP - RETURN NEXT tap; - END LOOP; - - -- Clean up and return. - PERFORM _cleanup(); - RETURN; -END; -$function$; - -create or replace procedure willing_count_calls_of(_proc_schema text, _proc_name text, _proc_args text) ---it counts of calls of a routine during execution - LANGUAGE plpgsql -AS $procedure$ -declare - _ddl text; -begin - insert into call_count(routine_schema, routine_name, call_cnt) values(_proc_schema, _proc_name, 0); - _ddl = ' - create or replace procedure ' || quote_ident(_proc_schema) || '.' || quote_ident(_proc_name) || _proc_args || ' - LANGUAGE plpgsql - AS $proc$ - begin - update call_count set call_cnt = call_cnt + 1 - where routine_schema = ' || quote_literal(_proc_schema) || ' - and routine_name = ' || quote_literal(_proc_name) || '; - end - $proc$;'; - execute _ddl; -end -$procedure$; - -create or replace function called_once(_proc_schema text, _proc_name text) ---Insures that a routine have been called only once -returns setof text as $$ -begin - return query select called_times(1, _proc_schema, _proc_name); -end $$ -LANGUAGE plpgsql; - create or replace function call_count( _call_count int , _func_schema name @@ -11898,3 +11723,40 @@ AS $function$ AND is_visible $function$ ; + +CREATE OR REPLACE VIEW tap_funky + AS + SELECT + p.oid AS oid, + n.nspname AS schema, + p.proname AS name, + pg_catalog.pg_get_userbyid(p.proowner) AS owner, + proc_name.args AS args, + lower(coalesce( + proc_return."returns", + proc_return.sys_returns)) AS "returns", + p.prolang AS langoid, + p.proisstrict AS is_strict, + _prokind(p.oid) AS kind, + p.prosecdef AS is_definer, + p.proretset AS returns_set, + p.provolatile::char AS volatility, + pg_catalog.pg_function_is_visible(p.oid) AS is_visible, + l.lanname AS langname +FROM pg_catalog.pg_proc p +JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid +LEFT JOIN pg_language l ON l.oid = p.prolang +LEFT JOIN LATERAL ( + SELECT + (n.nspname::text || '.'::text) || p.proname::text AS qualified, + array_to_string(p.proargtypes::regtype[], ',') AS args +) proc_name ON true +LEFT JOIN LATERAL ( + SELECT + CASE + WHEN n.nspname != 'pg_catalog' + THEN pg_get_function_result((concat(proc_name.qualified, '(', proc_name.args, ')')::regprocedure)::oid) + ELSE NULL + END AS "returns", + CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END || p.prorettype::regtype AS sys_returns +) AS proc_return ON TRUE; \ No newline at end of file diff --git a/test/expected/funcmock.out b/test/expected/funcmock.out new file mode 100644 index 000000000..2d1ef0f75 --- /dev/null +++ b/test/expected/funcmock.out @@ -0,0 +1,5 @@ +\unset ECHO +1..3 +ok 1 - mock scalar_function should pass +ok 2 - mock sql function returning a set should pass +ok 3 - mock plpgsql function returning a set should pass diff --git a/test/sql/funcmock.sql b/test/sql/funcmock.sql new file mode 100644 index 000000000..319b3db0f --- /dev/null +++ b/test/sql/funcmock.sql @@ -0,0 +1,81 @@ +\unset ECHO +\i test/setup.sql +-- \i sql/pgtap.sql + +SELECT plan(3); +--SELECT * FROM no_plan(); + +-- This will be rolled back. :-) +--SET track_functions = 'all'; + +-- This will be rolled back. :-) +SET client_min_messages = warning; + +create or replace function scalar_function() +returns time +language sql +as $$ + select now()::time; +$$; + +create or replace function set_sql_function() +returns table(id int, col text) +language sql +as $$ + select * FROM (VALUES(1, 'a'), (2, 'b')) AS t(id, col); +$$; + +create or replace function set_plpgsql_function() +returns table(id int, col text) +language plpgsql +as $$ +begin + RETURN query select * FROM (VALUES(1, 'a'), (2, 'b')) AS t(id, col); +END; +$$; + +RESET client_min_messages; + +CREATE FUNCTION test_mocking_functionality() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; + _hour_before time; + _mock_result time; +BEGIN + _hour_before = now() - INTERVAL '01:00'; + CALL mock_func('public', 'scalar_function', '()' + , _return_scalar_value => _hour_before::time); + _mock_result = scalar_function(); + + RETURN query SELECT * FROM check_test( + is(_mock_result, _hour_before), + TRUE, + 'mock scalar_function'); + + PREPARE mock_set_sql_function AS SELECT * FROM (VALUES(1, 'x'), (2, 'z')) AS t(id, col) ORDER BY id; + CALL mock_func('public', 'set_sql_function', '()' + , _return_set_value => 'mock_set_sql_function'); + PREPARE returned_set_sql_function AS SELECT * FROM set_sql_function() ORDER BY id; + + RETURN query SELECT * FROM check_test( + results_eq('returned_set_sql_function', 'mock_set_sql_function'), + TRUE, + 'mock sql function returning a set'); + + PREPARE mock_set_plpgsql_function AS SELECT * FROM (VALUES(1, 'w'), (2, 'q')) AS t(id, col) ORDER BY id; + CALL mock_func('public', 'set_plpgsql_function', '()' + , _return_set_value => 'mock_set_plpgsql_function'); + PREPARE returned_set_plpgsql_function AS SELECT * FROM set_plpgsql_function() ORDER BY id; + + RETURN query SELECT * FROM check_test( + results_eq('returned_set_plpgsql_function', 'mock_set_plpgsql_function'), + TRUE, + 'mock plpgsql function returning a set'); +END; +$$ LANGUAGE plpgsql; + +SELECT * FROM test_mocking_functionality(); + +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 401bcc2180300faf9086379656e4ca32ccf1960f Mon Sep 17 00:00:00 2001 From: v-maliutin Date: Wed, 8 Jan 2025 21:53:34 +0700 Subject: [PATCH 08/20] wrote self-tests on faking functionality --- sql/pgtap--1.3.3--1.3.4.sql | 12 +-- sql/pgtap.sql.in | 12 +-- test/expected/faketable.out | 16 ++++ test/sql/faketable.sql | 161 ++++++++++++++++++++++++++++++++++++ test/sql/funcmock.sql | 7 +- 5 files changed, 192 insertions(+), 16 deletions(-) create mode 100644 test/expected/faketable.out create mode 100644 test/sql/faketable.sql diff --git a/sql/pgtap--1.3.3--1.3.4.sql b/sql/pgtap--1.3.3--1.3.4.sql index 8bd3df905..f09f58b5d 100644 --- a/sql/pgtap--1.3.3--1.3.4.sql +++ b/sql/pgtap--1.3.3--1.3.4.sql @@ -210,7 +210,9 @@ create or replace function call_count( , _func_schema name , _func_name name , _func_args name[]) -returns setof text as $$ + RETURNS text + LANGUAGE plpgsql +AS $function$ declare _actual_call_count int; _track_functions_setting text; @@ -218,21 +220,19 @@ begin select current_setting('track_functions') into _track_functions_setting; if _track_functions_setting != 'all' then - return query select fail('track_functions setting is not set. Must be all'); - return; + return fail('track_functions setting is not set. Must be all'); end if; select calls into _actual_call_count from pg_stat_xact_user_functions where funcid = _get_func_oid(_func_schema, _func_name, _func_args); - return query select ok( + return ok( _actual_call_count = _call_count , format('routine %I.%I must have been called %L times, actual call count is %L' , _func_schema, _func_name, _call_count, _actual_call_count) ); -end $$ -LANGUAGE plpgsql; +end $function$; create or replace function drop_prepared_statement(_statements text[]) returns setof bool as $$ diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 524d58086..0cd4493db 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -11640,7 +11640,9 @@ create or replace function call_count( , _func_schema name , _func_name name , _func_args name[]) -returns setof text as $$ + RETURNS text + LANGUAGE plpgsql +AS $function$ declare _actual_call_count int; _track_functions_setting text; @@ -11648,21 +11650,19 @@ begin select current_setting('track_functions') into _track_functions_setting; if _track_functions_setting != 'all' then - return query select fail('track_functions setting is not set. Must be all'); - return; + return fail('track_functions setting is not set. Must be all'); end if; select calls into _actual_call_count from pg_stat_xact_user_functions where funcid = _get_func_oid(_func_schema, _func_name, _func_args); - return query select ok( + return ok( _actual_call_count = _call_count , format('routine %I.%I must have been called %L times, actual call count is %L' , _func_schema, _func_name, _call_count, _actual_call_count) ); -end $$ -LANGUAGE plpgsql; +end $function$; create or replace function drop_prepared_statement(_statements text[]) returns setof bool as $$ diff --git a/test/expected/faketable.out b/test/expected/faketable.out new file mode 100644 index 000000000..7a7da55c1 --- /dev/null +++ b/test/expected/faketable.out @@ -0,0 +1,16 @@ +\unset ECHO +1..14 +ok 1 - public.parent.id is primary key should pass +ok 2 - public.child.id is not primary key should pass +ok 3 - public.child.parent_id is not foreign key should pass +ok 4 - public.parent.id is not null should pass +ok 5 - public.parent.col is null should pass +ok 6 - public.child.id is null should pass +ok 7 - public.child.parent_id is null should pass +ok 8 - public.child.col is null should pass +ok 9 - table public.parent is empty should pass +ok 10 - table public.child is empty should pass +ok 11 - We can do insert into foreign key column should pass +ok 12 - public.scalar_function called once should pass +ok 13 - public.set_sql_function called twice should pass +ok 14 - public.set_sql_function(text) called once should pass diff --git a/test/sql/faketable.sql b/test/sql/faketable.sql new file mode 100644 index 000000000..8fbea7637 --- /dev/null +++ b/test/sql/faketable.sql @@ -0,0 +1,161 @@ +\unset ECHO +\i test/setup.sql +-- \i sql/pgtap.sql + +SELECT plan(14); +--SELECT * FROM no_plan(); + +-- This will be rolled back. :-) +SET track_functions = 'all'; +SET client_min_messages = warning; + +create or replace function public.scalar_function() +returns time +language plpgsql +as $$ +begin + return now()::time; +end; +$$; + +create or replace function public.set_sql_function() +returns table(id int, f text) +language sql +as $$ + select * from (values(1, 'a'), (2, 'b'), (3, 'c')) as t(id, f); +$$; + +create or replace function public.set_sql_function(_whatever text) +returns table(id int, f text) +language sql +as $$ + select * from (values(1, 'a' || _whatever), (2, 'b' || _whatever)) as t(id, f); +$$; + +CREATE TABLE public.parent( + id int NOT NULL, col text NOT NULL, + CONSTRAINT parent_pk PRIMARY KEY (id) +); + +CREATE TABLE public.child( + id int NOT NULL, + parent_id int NOT NULL, + col text NOT NULL, + CONSTRAINT child_pk PRIMARY KEY (id), + CONSTRAINT child_fk FOREIGN KEY (parent_id) REFERENCES parent(id) +); + +INSERT INTO public.parent(id, col) values(1, 'a'); + +INSERT INTO public.child(id, parent_id, col) values(1, 1, 'b'); + +RESET client_min_messages; + +CREATE FUNCTION test_faking_functionality() RETURNS SETOF TEXT AS $$ +BEGIN + CALL fake_table( + '{public.parent}'::text[], + _make_table_empty => TRUE, + _leave_primary_key => TRUE, + _drop_not_null => FALSE); + + CALL fake_table( + '{public.child}'::text[], + _make_table_empty => TRUE, + _leave_primary_key => FALSE, + _drop_not_null => TRUE); + + RETURN query SELECT * FROM check_test( + col_is_pk('public', 'parent', '{id}'::name[]), + TRUE, + 'public.parent.id is primary key'); + + RETURN query SELECT * FROM check_test( + col_isnt_pk('public', 'child', 'id'), + TRUE, + 'public.child.id is not primary key'); + + RETURN query SELECT * FROM check_test( + col_isnt_fk('public', 'child', 'parent_id'), + TRUE, + 'public.child.parent_id is not foreign key'); + + RETURN query SELECT * FROM check_test( + col_not_null('public', 'parent', 'id', ''), + TRUE, + 'public.parent.id is not null'); + + RETURN query SELECT * FROM check_test( + col_not_null('public', 'parent', 'col', ''), + TRUE, + 'public.parent.col is null'); + + RETURN query SELECT * FROM check_test( + col_is_null('public', 'child', 'id', ''), + TRUE, + 'public.child.id is null'); + + RETURN query SELECT * FROM check_test( + col_is_null('public', 'child', 'parent_id', ''), + TRUE, + 'public.child.parent_id is null'); + + RETURN query SELECT * FROM check_test( + col_is_null('public', 'child', 'col', ''), + TRUE, + 'public.child.col is null'); + + PREPARE parent_all AS SELECT * FROM public.parent; + PREPARE child_all AS SELECT * FROM public.child; + + RETURN query SELECT * FROM check_test( + is_empty('parent_all'), + TRUE, + 'table public.parent is empty'); + + RETURN query SELECT * FROM check_test( + is_empty('child_all'), + TRUE, + 'table public.child is empty'); + + RETURN query SELECT * FROM check_test( + lives_ok('INSERT INTO child(id, parent_id, col) values(1, 108, ''z'')'), + TRUE, + 'We can do insert into foreign key column'); +END; +$$ LANGUAGE plpgsql; + + +SELECT * FROM test_faking_functionality(); + +CREATE FUNCTION test_call_count_functionality() RETURNS SETOF TEXT AS $$ +BEGIN + perform public.scalar_function(); + perform public.set_sql_function(); + perform public.set_sql_function(); + perform public.set_sql_function('whatever'); + + RETURN query SELECT * FROM check_test( + call_count(1, 'public', 'scalar_function', '{}'::name[]), + TRUE, + 'public.scalar_function called once'); + + RETURN query SELECT * FROM check_test( + call_count(2, 'public', 'set_sql_function', '{}'::name[]), + TRUE, + 'public.set_sql_function called twice'); + + RETURN query SELECT * FROM check_test( + call_count(1, 'public', 'set_sql_function', '{text}'::name[]), + TRUE, + 'public.set_sql_function(text) called once'); +END; +$$ LANGUAGE plpgsql; + +SELECT * FROM test_call_count_functionality(); + +RESET track_functions; + +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; diff --git a/test/sql/funcmock.sql b/test/sql/funcmock.sql index 319b3db0f..85fde546e 100644 --- a/test/sql/funcmock.sql +++ b/test/sql/funcmock.sql @@ -11,21 +11,21 @@ SELECT plan(3); -- This will be rolled back. :-) SET client_min_messages = warning; -create or replace function scalar_function() +create or replace function public.scalar_function() returns time language sql as $$ select now()::time; $$; -create or replace function set_sql_function() +create or replace function public.set_sql_function() returns table(id int, col text) language sql as $$ select * FROM (VALUES(1, 'a'), (2, 'b')) AS t(id, col); $$; -create or replace function set_plpgsql_function() +create or replace function public.set_plpgsql_function() returns table(id int, col text) language plpgsql as $$ @@ -38,7 +38,6 @@ RESET client_min_messages; CREATE FUNCTION test_mocking_functionality() RETURNS SETOF TEXT AS $$ DECLARE - tap record; _hour_before time; _mock_result time; BEGIN From c95845557adc836c52456d52357029c5ede9ab28 Mon Sep 17 00:00:00 2001 From: mslava Date: Thu, 13 Mar 2025 18:18:29 +0700 Subject: [PATCH 09/20] Prettying up examples --- examples/mocking_and_faking.sql | 46 +++++++++++++------------- sql/pgtap--1.3.1--1.3.2.sql | 58 --------------------------------- sql/pgtap--1.3.3--1.3.4.sql | 2 +- sql/pgtap.sql.in | 2 +- 4 files changed, 26 insertions(+), 82 deletions(-) diff --git a/examples/mocking_and_faking.sql b/examples/mocking_and_faking.sql index 63dd64b9c..9dd383766 100644 --- a/examples/mocking_and_faking.sql +++ b/examples/mocking_and_faking.sql @@ -1,19 +1,29 @@ -create schema if not exists tap; - +/* DROP EXTENSION pgtap; CREATE EXTENSION pgtap schema tap; +*/ +create schema if not exists tap; create schema if not exists pgconf; - create schema if not exists tests; set search_path to public, tap, pgconf; +set track_functions = 'all' ; + +drop function if exists pgconf.get_osv_slice(int, int, int); +drop function if exists pgconf.get_tree_of(int); +drop function if exists pgconf.time_machine_now(); +drop procedure if exists pgconf.make_osv_report(); +drop procedure if exists tests.create_test_data(); +drop function if exists tests.test_osv_on_time(); +drop function if exists tests.test_osv_not_on_time(); +drop function if exists tests.test_get_tree_of_called_times(); -drop table pgconf.osv; -drop table pgconf.transactions; -drop table pgconf.analytic; -drop table pgconf.account; +drop table if exists pgconf.osv; +drop table if exists pgconf.transactions; +drop table if exists pgconf.analytic; +drop table if exists pgconf.account; create table pgconf.account( id int generated always as identity primary key @@ -75,7 +85,7 @@ with recursive account_tree as ( from account_tree t join pgconf.account a on a.parent_id = t.id -)/*1*/search depth first by num set account_order +)search depth first by num set account_order select t.account_order::text[] , t.num as account_num @@ -96,11 +106,9 @@ on a2.id = o.subconto_2_id left join pgconf.analytic a3 on a3.id = o.subconto_3_id where - /*2*/pgconf.time_machine_now() between '12:00'::time and '15:00'::time + pgconf.time_machine_now() between '12:00'::time and '15:00'::time and (_account_id is null and o.subconto_1_id is null and o.subconto_2_id is null and o.subconto_3_id is null) ---where --- ((_subc_2 is null or o.subconto_2_id = _subc_2) and subconto_2 is not null); -/*1*/order by t.account_order +order by t.account_order ; end; $$; @@ -259,7 +267,7 @@ begin select tap.results_eq( 'returned', 'expected', - 'Счета должны быть отсортированы "сначала в глубину"' + 'Accounts must be sorted in depth first.' ); @@ -297,7 +305,7 @@ begin return query select tap.is_empty( 'returned', - 'Время не пришло. ОСВ делать нельзя' + 'It is not good time to make osv report.' ); perform tap.drop_prepared_statement('{returned}'::text[]); @@ -324,7 +332,7 @@ begin return query select tap.isnt_empty( 'returned', - 'Время пришло. ОСВ делать можно' + 'Time has come. We can make osv report.' ); perform tap.drop_prepared_statement('{expected}'::text[]); @@ -346,12 +354,6 @@ begin end; $$; -set track_functions = 'all' ; - -set search_path to public, tap, pgconf; - select * from tap.runtests('tests', '^test_'); -select * from tap.runtests('tests', 'test_osv_on_time'); - -order by account_id, subconto_3_id nulls last, subconto_2_id nulls last, subconto_1_id nulls last +select * from tap.runtests('tests', 'test_osv_on_time'); \ No newline at end of file diff --git a/sql/pgtap--1.3.1--1.3.2.sql b/sql/pgtap--1.3.1--1.3.2.sql index b85bc5cb3..7521a59ac 100644 --- a/sql/pgtap--1.3.1--1.3.2.sql +++ b/sql/pgtap--1.3.1--1.3.2.sql @@ -266,13 +266,6 @@ BEGIN RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); END LOOP; - -- Create a temp table to collect call count - execute 'create temp table call_count( - routine_schema text not null - , routine_name text not null - , call_cnt int - ) ON COMMIT DROP;'; - -- Run the actual test function. FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); @@ -358,57 +351,6 @@ BEGIN END; $function$; -create or replace procedure willing_count_calls_of(_proc_schema text, _proc_name text, _proc_args text) - LANGUAGE plpgsql -AS $procedure$ -declare - _ddl text; -begin - insert into call_count(routine_schema, routine_name, call_cnt) values(_proc_schema, _proc_name, 0); - _ddl = ' - create or replace procedure ' || quote_ident(_proc_schema) || '.' || quote_ident(_proc_name) || _proc_args || ' - LANGUAGE plpgsql - AS $proc$ - begin - update call_count set call_cnt = call_cnt + 1 - where routine_schema = ' || quote_literal(_proc_schema) || ' - and routine_name = ' || quote_literal(_proc_name) || '; - end - $proc$;'; - raise notice '%', _ddl; - execute _ddl; -end -$procedure$; - -create or replace function called_once(_proc_schema text, _proc_name text) -returns setof text as $$ -begin - return query select called_times(1, _proc_schema, _proc_name); -end $$ -LANGUAGE plpgsql; - -create or replace function called_times(_call_count int, _proc_schema text, _proc_name text) -returns setof text as $$ -declare - _actual_call_count int; -begin - select - call_cnt - into - _actual_call_count - from - call_count - where - routine_schema = _proc_schema - and routine_name = _proc_name; - - return query select ok( - _actual_call_count = _call_count - , format('routine %L.%L must have been called %L times', _proc_schema, _proc_name, _call_count) - ); -end $$ -LANGUAGE plpgsql; - create or replace function drop_prepared_statement(_statement_name text) returns bool as $$ begin diff --git a/sql/pgtap--1.3.3--1.3.4.sql b/sql/pgtap--1.3.3--1.3.4.sql index f09f58b5d..6643e96b7 100644 --- a/sql/pgtap--1.3.3--1.3.4.sql +++ b/sql/pgtap--1.3.3--1.3.4.sql @@ -229,7 +229,7 @@ begin return ok( _actual_call_count = _call_count - , format('routine %I.%I must have been called %L times, actual call count is %L' + , format('routine %I.%I must has been called %L times, actual call count is %L' , _func_schema, _func_name, _call_count, _actual_call_count) ); end $function$; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 0cd4493db..91487d6cd 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -11659,7 +11659,7 @@ begin return ok( _actual_call_count = _call_count - , format('routine %I.%I must have been called %L times, actual call count is %L' + , format('routine %I.%I must has been called %L times, actual call count is %L' , _func_schema, _func_name, _call_count, _actual_call_count) ); end $function$; From 19c700aada2156ddc3f787fe0c3dc275bd117068 Mon Sep 17 00:00:00 2001 From: mslava Date: Thu, 13 Mar 2025 23:55:51 +0700 Subject: [PATCH 10/20] Leave a single version --- sql/pgtap--1.3.1--1.3.2.sql | 360 ------------------------------------ 1 file changed, 360 deletions(-) diff --git a/sql/pgtap--1.3.1--1.3.2.sql b/sql/pgtap--1.3.1--1.3.2.sql index 7521a59ac..d0dbd80cb 100644 --- a/sql/pgtap--1.3.1--1.3.2.sql +++ b/sql/pgtap--1.3.1--1.3.2.sql @@ -32,363 +32,3 @@ BEGIN EXCEPTION WHEN OTHERS THEN RETURN NULL; END; $$ LANGUAGE PLPGSQL STABLE; - ---added a three columns: "args", "returns", "langname" used in mock_func function -CREATE OR REPLACE VIEW tap_funky -AS -SELECT p.oid, - n.nspname AS schema, - p.proname AS name, - pg_get_userbyid(p.proowner) AS owner, - arg._types as args, - proc_return."returns", - p.prolang AS langoid, - p.proisstrict AS is_strict, - tap._prokind(p.oid) AS kind, - p.prosecdef AS is_definer, - p.proretset AS returns_set, - p.provolatile::character(1) AS volatility, - pg_function_is_visible(p.oid) AS is_visible, - l.lanname AS langname -FROM - pg_proc p -JOIN - pg_namespace n -ON - p.pronamespace = n.oid -LEFT JOIN - pg_language l -ON - l.oid = p.prolang -left join lateral ( - select string_agg(nullif(_type, '')::regtype::text, ', ') as _types - from unnest(regexp_split_to_array(p.proargtypes::text, ' ')) as _type -) as arg on true - LEFT JOIN LATERAL ( - SELECT (n.nspname::text || '.'::text) || p.proname::text AS qualified -) proc_name ON true -left join lateral ( - select - case - when n.nspname != 'pg_catalog' - then pg_get_function_result((concat(proc_name.qualified, '(', arg._types, ')')::regprocedure)::oid) - else null - end AS "returns" -) as proc_return on true; - ---this procedure creates a mock in place of a real function -create or replace procedure mock_func( - in _func_schema text - , in _func_name text - , in _func_args text - , in _return_value anyelement -) ---creates mock in place of a real function - LANGUAGE plpgsql -AS $procedure$ -declare - _mock_ddl text; - _func_result_type text; - _func_qualified_name text; - _func_language text; -begin - select - "returns" - , langname - into - _func_result_type - , _func_language - from - tap_funky - where - "schema" = _func_schema - and "name" = _func_name; - - _mock_ddl = ' - create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' - RETURNS ' || _func_result_type || ' - LANGUAGE ' || _func_language || ' - AS $function$ - select ' || quote_nullable(_return_value) || '::' || pg_typeof(_return_value) || '; - $function$;'; - execute _mock_ddl; -end $procedure$; - -CREATE OR REPLACE PROCEDURE fake_table( - IN _table_schema text[], - IN _table_name text[], - in _make_table_empty boolean default false, - IN _drop_not_null boolean DEFAULT false, - IN _drop_collation boolean DEFAULT false -) ---It frees a table from any constraint (we call such a table as a fake) ---faked table is a full copy of _table_name, but has no any constraint ---without foreign and primary things you can do whatever you want in testing context - LANGUAGE plpgsql -AS $procedure$ -declare - _table record; - _fk_table record; - _fake_ddl text; - _not_null_ddl text; -begin - for _table in - select - quote_ident(table_schema) table_schema, - quote_ident(table_name) table_name, - table_schema table_schema_l, - table_name table_name_l - from - unnest(_table_schema, _table_name) as t(table_schema, table_name) - loop - for _fk_table in - -- collect all table's relations including primary key and unique constraint - select distinct * - from ( - select - fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord - from - pg_all_foreign_keys - where - fk_schema_name = _table.table_schema_l and fk_table_name = _table.table_name_l - union all - select - fk_schema_name table_schema, fk_table_name table_name, fk_constraint_name constraint_name, 1 as ord - from - pg_all_foreign_keys - where - pk_schema_name = _table.table_schema_l and pk_table_name = _table.table_name_l - union all - select - table_schema, table_name, constraint_name, 2 as ord - from - information_schema.table_constraints - where - table_schema = _table.table_schema_l - and table_name = _table.table_name_l - and constraint_type in ('PRIMARY KEY', 'UNIQUE') - ) as t - order by ord - loop - _fake_ddl = 'alter table ' || _fk_table.table_schema || '.' || _fk_table.table_name || ' - drop constraint ' || _fk_table.constraint_name || ';'; - execute _fake_ddl; - end loop; - - if _make_table_empty then - _fake_ddl = 'truncate table ' || _table.table_schema || '.' || _table.table_name || ';'; - execute _fake_ddl; - end if; - - --Free table from not null constraints - _fake_ddl = 'alter table ' || _table.table_schema || '.' || _table.table_name || ' '; - if _drop_not_null then - select - string_agg('alter column ' || t.attname || ' drop not null', ', ') - into - _not_null_ddl - from - pg_catalog.pg_attribute t - where t.attrelid = (_table.table_schema || '.' || _table.table_name)::regclass - and t.attnum > 0 and attnotnull; - - _fake_ddl = _fake_ddl || _not_null_ddl || ';'; - else - _fake_ddl = null; - end if; - - if _fake_ddl is not null then - execute _fake_ddl; - end if; - end loop; -end $procedure$; - -CREATE OR REPLACE FUNCTION _runner(text[], text[], text[], text[], text[]) - RETURNS SETOF text - LANGUAGE plpgsql -AS $function$ -DECLARE - startup ALIAS FOR $1; - shutdown ALIAS FOR $2; - setup ALIAS FOR $3; - teardown ALIAS FOR $4; - tests ALIAS FOR $5; - tap TEXT; - tfaild INTEGER := 0; - ffaild INTEGER := 0; - tnumb INTEGER := 0; - fnumb INTEGER := 0; - tok BOOLEAN := TRUE; -BEGIN - BEGIN - -- No plan support. - PERFORM * FROM no_plan(); - FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; - EXCEPTION - -- Catch all exceptions and simply rethrow custom exceptions. This - -- will roll back everything in the above block. - WHEN raise_exception THEN RAISE EXCEPTION '%', SQLERRM; - END; - - -- Record how startup tests have failed. - tfaild := num_failed(); - - FOR i IN 1..COALESCE(array_upper(tests, 1), 0) LOOP - - -- What subtest are we running? - RETURN NEXT diag_test_name('Subtest: ' || tests[i]); - - -- Reset the results. - tok := TRUE; - tnumb := COALESCE(_get('curr_test'), 0); - - IF tnumb > 0 THEN - EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; - PERFORM _set('curr_test', 0); - PERFORM _set('failed', 0); - END IF; - - DECLARE - errstate text; - errmsg text; - detail text; - hint text; - context text; - schname text; - tabname text; - colname text; - chkname text; - typname text; - BEGIN - BEGIN - -- Run the setup functions. - FOR tap IN SELECT * FROM _runem(setup, false) LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Run the actual test function. - FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Run the teardown functions. - FOR tap IN SELECT * FROM _runem(teardown, false) LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); - END LOOP; - - -- Emit the plan. - fnumb := COALESCE(_get('curr_test'), 0); - RETURN NEXT ' 1..' || fnumb; - - -- Emit any error messages. - IF fnumb = 0 THEN - RETURN NEXT ' # No tests run!'; - tok = false; - ELSE - -- Report failures. - ffaild := num_failed(); - IF ffaild > 0 THEN - tok := FALSE; - RETURN NEXT ' ' || diag( - 'Looks like you failed ' || ffaild || ' test' || - CASE ffaild WHEN 1 THEN '' ELSE 's' END - || ' of ' || fnumb - ); - END IF; - END IF; - - EXCEPTION WHEN OTHERS THEN - -- Something went wrong. Record that fact. - errstate := SQLSTATE; - errmsg := SQLERRM; - GET STACKED DIAGNOSTICS - detail = PG_EXCEPTION_DETAIL, - hint = PG_EXCEPTION_HINT, - context = PG_EXCEPTION_CONTEXT, - schname = SCHEMA_NAME, - tabname = TABLE_NAME, - colname = COLUMN_NAME, - chkname = CONSTRAINT_NAME, - typname = PG_DATATYPE_NAME; - END; - - -- Always raise an exception to rollback any changes. - RAISE EXCEPTION '__TAP_ROLLBACK__'; - - EXCEPTION WHEN raise_exception THEN - IF errmsg IS NOT NULL THEN - -- Something went wrong. Emit the error message. - tok := FALSE; - RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( - errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname - )), '^', ' ', 'gn'); - errmsg := NULL; - END IF; - END; - - -- Restore the sequence. - EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; - PERFORM _set('curr_test', tnumb); - PERFORM _set('failed', tfaild); - - -- Record this test. - RETURN NEXT ok(tok, tests[i]); - IF NOT tok THEN tfaild := tfaild + 1; END IF; - - END LOOP; - - -- Run the shutdown functions. - FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; - - -- Finish up. - FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP - RETURN NEXT tap; - END LOOP; - - -- Clean up and return. - PERFORM _cleanup(); - RETURN; -END; -$function$; - -create or replace function drop_prepared_statement(_statement_name text) -returns bool as $$ -begin - if exists(select * from pg_prepared_statements where "name" = _statement_name) then - EXECUTE format('deallocate %I;', _statement_name); - return true; - end if; - return false; -end -$$ -language plpgsql; - -create or replace procedure print_table_as_json(in _table_schema text, in _table_name text) - language plpgsql -AS $procedure$ -declare - _ddl text; - _json text; - _columns text; ---returns a query which you can execute and see your table as normal dataset ---note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows -begin - _ddl = ' - select json_agg( - array(select ' || quote_ident(_table_name) || ' from ' || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || ' limit 1000 - )) as j;'; - execute _ddl into _json; - _json = '[' || ltrim(rtrim(_json::text, ']'), '[') || ']'; - - select string_agg(concat(c.column_name, ' ', case when lower(c.data_type) = 'array' then e.data_type || '[]' else c.data_type end), ', ') - into _columns - from information_schema."columns" c - left join information_schema.element_types e - on ((c.table_catalog, c.table_schema, c.table_name, 'TABLE', c.dtd_identifier) - = (e.object_catalog, e.object_schema, e.object_name, e.object_type, e.collection_type_identifier)) - where c.table_schema = _table_schema - and c.table_name = _table_name; - - _json = $$select * from /*$$ || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || $$*/ json_to_recordset('$$ || _json || $$') as t($$ || _columns || $$)$$; - raise notice '%', _json; -end $procedure$; \ No newline at end of file From 8dce627f21e8c4de4717a3feb3c6b3d5289ed144 Mon Sep 17 00:00:00 2001 From: mslava Date: Sun, 24 Aug 2025 20:56:45 +0700 Subject: [PATCH 11/20] Created a new function to print a query instead of a table. Just more convenient way to debug. --- examples/mocking_and_faking.sql | 4 ++-- sql/pgtap--1.3.3--1.3.4.sql | 27 +++++++++++++++++++++++---- sql/pgtap--unpackaged--0.91.0.sql | 3 ++- sql/pgtap.sql.in | 22 ++++++++++++++++++++-- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/examples/mocking_and_faking.sql b/examples/mocking_and_faking.sql index 9dd383766..2468e3f19 100644 --- a/examples/mocking_and_faking.sql +++ b/examples/mocking_and_faking.sql @@ -185,9 +185,9 @@ language plpgsql as $$ begin call tap.fake_table( - '{pgconf.account, pgconf.analytic, pgconf.osv, pgconf.transactions}'::text[], - _leave_primary_key => false, + _table_ident => '{pgconf.account, pgconf.analytic, pgconf.osv, pgconf.transactions}'::text[], _make_table_empty => true, + _leave_primary_key => false, _drop_not_null => false, _drop_collation => false ); diff --git a/sql/pgtap--1.3.3--1.3.4.sql b/sql/pgtap--1.3.3--1.3.4.sql index 6643e96b7..1f25d341e 100644 --- a/sql/pgtap--1.3.3--1.3.4.sql +++ b/sql/pgtap--1.3.3--1.3.4.sql @@ -252,17 +252,18 @@ $$ language plpgsql; -create or replace procedure print_table_as_json(_table_schema text, _table_name text) +create or replace procedure print_table_as_json(in _table_schema text, in _table_name text) language plpgsql AS $procedure$ -declare +declare _ddl text; _json text; _columns text; --returns a query which you can execute and see your table as normal dataset ---note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows +--you can find the returned query in the output window in DBeaver, where we see raise notice command output +--note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows begin - _ddl = ' + _ddl = ' select json_agg( array(select ' || quote_ident(_table_name) || ' from ' || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || ' limit 1000 )) as j;'; @@ -282,6 +283,24 @@ begin raise notice '%', _json; end $procedure$; +create or replace procedure print_query_as_json(in _prepared_statement_name text) + language plpgsql +as $procedure$ +declare + _ddl text; + _table_name text; +--returns a query which you can execute and see your table as normal dataset +--you can find the returned query in the output window in DBeaver, where we see raise notice command output +--note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows +begin + _table_name = _prepared_statement_name || '_' || gen_random_uuid(); + _ddl = format('create table public.%1I as execute %2s', _table_name, _prepared_statement_name); + execute _ddl; + call print_table_as_json('public', _table_name::text); +end; +$procedure$; + + CREATE OR REPLACE FUNCTION _get_func_oid(name, name, name[]) RETURNS oid LANGUAGE sql diff --git a/sql/pgtap--unpackaged--0.91.0.sql b/sql/pgtap--unpackaged--0.91.0.sql index 9a9bcf57d..dac20052e 100644 --- a/sql/pgtap--unpackaged--0.91.0.sql +++ b/sql/pgtap--unpackaged--0.91.0.sql @@ -717,5 +717,6 @@ ALTER EXTENSION pgtap ADD PROCEDURE mock_func( TEXT, TEXT, TEXT, ANYELEMENT ); ALTER EXTENSION pgtap ADD PROCEDURE fake_table( TEXT[], TEXT[], BOOL, BOOL ); ALTER EXTENSION pgtap ADD FUNCTION call_count( INT, TEXT, TEXT ); ALTER EXTENSION pgtap ADD FUNCTION drop_prepared_statement( TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION print_table_as_json( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD PROCEDURE print_table_as_json( TEXT, TEXT ); +ALTER EXTENSION pgtap ADD PROCEDURE print_query_as_json( TEXT ); ALTER EXTENSION pgtap ADD FUNCTION _get_func_oid(name, name[]); \ No newline at end of file diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 91487d6cd..60ec9e4ff 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -11689,9 +11689,10 @@ declare _json text; _columns text; --returns a query which you can execute and see your table as normal dataset ---note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows +--you can find the returned query in the output window in DBeaver, where we see raise notice command output +--note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows begin - _ddl = ' + _ddl = ' select json_agg( array(select ' || quote_ident(_table_name) || ' from ' || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || ' limit 1000 )) as j;'; @@ -11711,6 +11712,23 @@ begin raise notice '%', _json; end $procedure$; +create or replace procedure print_query_as_json(in _prepared_statement_name text) + language plpgsql +as $procedure$ +declare + _ddl text; + _table_name text; +--returns a query which you can execute and see your table as normal dataset +--you can find the returned query in the output window in DBeaver, where we see raise notice command output +--note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows +begin + _table_name = _prepared_statement_name || '_' || gen_random_uuid(); + _ddl = format('create table public.%1I as execute %2s', _table_name, _prepared_statement_name); + execute _ddl; + call print_table_as_json('public', _table_name::text); +end; +$procedure$; + CREATE OR REPLACE FUNCTION _get_func_oid(name, name, name[]) RETURNS oid LANGUAGE sql From 80fdb8a46e41845b055be6b40dc74156d6247c61 Mon Sep 17 00:00:00 2001 From: mslava Date: Sun, 24 Aug 2025 21:31:49 +0700 Subject: [PATCH 12/20] No modifications in unpackaged --- sql/pgtap--unpackaged--0.91.0.sql | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/sql/pgtap--unpackaged--0.91.0.sql b/sql/pgtap--unpackaged--0.91.0.sql index dac20052e..a96f282b1 100644 --- a/sql/pgtap--unpackaged--0.91.0.sql +++ b/sql/pgtap--unpackaged--0.91.0.sql @@ -711,12 +711,4 @@ ALTER EXTENSION pgtap ADD FUNCTION columns_are( NAME, NAME[], TEXT ); ALTER EXTENSION pgtap ADD FUNCTION columns_are( NAME, NAME[] ); ALTER EXTENSION pgtap ADD FUNCTION _get_db_owner( NAME ); ALTER EXTENSION pgtap ADD FUNCTION db_owner_is ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION db_owner_is ( NAME, NAME ); - -ALTER EXTENSION pgtap ADD PROCEDURE mock_func( TEXT, TEXT, TEXT, ANYELEMENT ); -ALTER EXTENSION pgtap ADD PROCEDURE fake_table( TEXT[], TEXT[], BOOL, BOOL ); -ALTER EXTENSION pgtap ADD FUNCTION call_count( INT, TEXT, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION drop_prepared_statement( TEXT ); -ALTER EXTENSION pgtap ADD PROCEDURE print_table_as_json( TEXT, TEXT ); -ALTER EXTENSION pgtap ADD PROCEDURE print_query_as_json( TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION _get_func_oid(name, name[]); \ No newline at end of file +ALTER EXTENSION pgtap ADD FUNCTION db_owner_is ( NAME, NAME ); \ No newline at end of file From 6226807c82eab4e73a029113e8e7b38248493a46 Mon Sep 17 00:00:00 2001 From: mslava Date: Sun, 24 Aug 2025 21:48:14 +0700 Subject: [PATCH 13/20] No modifications in unpackaged --- sql/pgtap--unpackaged--0.91.0.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/pgtap--unpackaged--0.91.0.sql b/sql/pgtap--unpackaged--0.91.0.sql index a96f282b1..e8875c32f 100644 --- a/sql/pgtap--unpackaged--0.91.0.sql +++ b/sql/pgtap--unpackaged--0.91.0.sql @@ -711,4 +711,4 @@ ALTER EXTENSION pgtap ADD FUNCTION columns_are( NAME, NAME[], TEXT ); ALTER EXTENSION pgtap ADD FUNCTION columns_are( NAME, NAME[] ); ALTER EXTENSION pgtap ADD FUNCTION _get_db_owner( NAME ); ALTER EXTENSION pgtap ADD FUNCTION db_owner_is ( NAME, NAME, TEXT ); -ALTER EXTENSION pgtap ADD FUNCTION db_owner_is ( NAME, NAME ); \ No newline at end of file +ALTER EXTENSION pgtap ADD FUNCTION db_owner_is ( NAME, NAME ); From f57c7e00d48827b4f098bda791fcca86b848d928 Mon Sep 17 00:00:00 2001 From: mslava Date: Tue, 23 Sep 2025 12:59:20 +0700 Subject: [PATCH 14/20] Function instead of procedures + format function instead of || --- examples/mocking_and_faking.sql | 34 ++-- sql/pgtap--1.3.3--1.3.4.sql | 230 ++++++++++--------------- sql/pgtap.sql.in | 289 +++++++++++++++----------------- test/sql/faketable.sql | 4 +- test/sql/funcmock.sql | 10 +- 5 files changed, 247 insertions(+), 320 deletions(-) diff --git a/examples/mocking_and_faking.sql b/examples/mocking_and_faking.sql index 2468e3f19..3d070adcc 100644 --- a/examples/mocking_and_faking.sql +++ b/examples/mocking_and_faking.sql @@ -184,7 +184,7 @@ create or replace procedure tests.create_test_data() language plpgsql as $$ begin - call tap.fake_table( + perform tap.fake_table( _table_ident => '{pgconf.account, pgconf.analytic, pgconf.osv, pgconf.transactions}'::text[], _make_table_empty => true, _leave_primary_key => false, @@ -207,7 +207,7 @@ begin insert into pgconf.account(parent_id, num) select id, '01.01.01' from pgconf.account where num = '03.01'; - + insert into pgconf.account(parent_id, num) select id, '01.01.02' from pgconf.account where num = '03.01'; @@ -243,16 +243,16 @@ create or replace function tests.test_osv_ordered_in_depth() returns setof text language plpgsql as $$ -begin +begin -- GIVEN call tests.create_test_data(); - call tap.mock_func('pgconf', 'time_machine_now', '()' + perform tap.mock_func('pgconf', 'time_machine_now', '()' , _return_scalar_value => '13:00'::time); - + -- WHEN perform tap.drop_prepared_statement('{expected, returned}'::text[]); - prepare expected as + prepare expected as select num::text from (values ('01', 1), ('03.01', 2), ('01.01.01', 2), ('01.01.01.01', 3), ('01.01.01.02', 4) , ('01.01.02', 5), ('01.01.02.01', 6) @@ -271,13 +271,13 @@ begin ); - create table pgconf.slice as + create table pgconf.slice as select * from pgconf.get_osv_slice(null, null, null); - call tap.print_table_as_json('pgconf', 'slice'); - call tap.print_table_as_json('pgconf', 'account'); + perform tap.print_table_as_json('pgconf', 'slice'); + perform tap.print_table_as_json('pgconf', 'account'); - -- WHEN + -- WHEN perform tap.drop_prepared_statement('{expected, returned}'::text[]); end; $$; @@ -286,16 +286,16 @@ create or replace function tests.test_osv_on_time() returns setof text language plpgsql as $$ -begin +begin -- GIVEN call tests.create_test_data(); - call tap.mock_func('pgconf', 'time_machine_now', '()' + perform tap.mock_func('pgconf', 'time_machine_now', '()' , _return_scalar_value => '15:01'::time); create table pgconf.x as select * from pgconf.time_machine_now(); - call tap.print_table_as_json('pgconf', 'x'); + perform tap.print_table_as_json('pgconf', 'x'); - -- WHEN + -- WHEN perform tap.drop_prepared_statement('{returned}'::text[]); prepare returned as @@ -316,11 +316,11 @@ create or replace function tests.test_osv_not_on_time() returns setof text language plpgsql as $$ -begin +begin -- GIVEN call tests.create_test_data(); - call tap.mock_func('pgconf', 'time_machine_now', '()' - , _return_set_value => null, _return_scalar_value => '13:00'::time); + perform tap.mock_func('pgconf', 'time_machine_now', '()' + , _return_set_value => null::text, _return_scalar_value => '13:00'::time); -- WHEN perform tap.drop_prepared_statement('{returned}'::text[]); diff --git a/sql/pgtap--1.3.3--1.3.4.sql b/sql/pgtap--1.3.3--1.3.4.sql index df42c52f0..00b807d25 100644 --- a/sql/pgtap--1.3.3--1.3.4.sql +++ b/sql/pgtap--1.3.3--1.3.4.sql @@ -16,40 +16,20 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; ---added a column "langname" used in mock_func function -CREATE OR REPLACE VIEW tap_funky - AS SELECT p.oid AS oid, - n.nspname AS schema, - p.proname AS name, - pg_catalog.pg_get_userbyid(p.proowner) AS owner, - array_to_string(p.proargtypes::regtype[], ',') AS args, - CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END - || p.prorettype::regtype AS returns, - p.prolang AS langoid, - p.proisstrict AS is_strict, - _prokind(p.oid) AS kind, - p.prosecdef AS is_definer, - p.proretset AS returns_set, - p.provolatile::char AS volatility, - pg_catalog.pg_function_is_visible(p.oid) AS is_visible, - l.lanname AS langname - FROM pg_catalog.pg_proc p - JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid - LEFT JOIN pg_language l ON l.oid = p.prolang -; - ---this procedure creates a mock in place of a real function -create or replace procedure mock_func( + +--this function creates a mock in place of a real function +create or replace function mock_func( _func_schema text , _func_name text , _func_args text , _return_set_value text default null , _return_scalar_value anyelement default null::text ) +returns void --creates a mock in place of a real function LANGUAGE plpgsql -AS $procedure$ -declare +AS $function$ +declare _mock_ddl text; _func_result_type text; _func_qualified_name text; @@ -63,73 +43,79 @@ begin and "name" = _func_name; if _func_language = 'sql' and _returns_set then - _mock_ddl = ' - create or replace function ' || quote_ident(_func_schema) || '.__' || quote_ident(_func_name) || '(_name text) - returns ' || _func_result_type || ' + _mock_ddl = format(' + create or replace function %1$I.__%2$I(_name text) + returns %3$s language plpgsql - AS $function$ + AS %5$sfunction%5$s begin - return query execute _query(' || quote_literal(_return_set_value) || '); + return query execute _query(%4$L); end; - $function$;'; + %5$sfunction%5$s;', + _func_schema/*1*/, _func_name/*2*/, _func_result_type/*3*/, _return_set_value/*4*/, '$'/*5*/); execute _mock_ddl; - _mock_ddl = ' - create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' - returns ' || _func_result_type || ' - language ' || _func_language || ' - AS $function$ - select * from ' || quote_ident(_func_schema) || '.__' || quote_ident(_func_name) || - '(' || quote_literal(_return_set_value) || '); - $function$;'; + _mock_ddl = format(' + create or replace function %1$I.%2$I %3$s + returns %4$s + language %5$s + AS %7$sfunction%7$s + select * from %1$I.__%2$I ( %6$s ); + %7$sfunction%7$s;', + _func_schema/*1*/, _func_name/*2*/, _func_args/*3*/, _func_result_type/*4*/, + _func_language/*5*/, _return_set_value/*6*/, '$'/*7*/); execute _mock_ddl; end if; - + if _func_language = 'plpgsql' and _returns_set then - _mock_ddl = ' - create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' - returns ' || _func_result_type || ' + _mock_ddl = format(' + create or replace function %1$I.%2$I %3$s + returns %4$s language plpgsql - AS $function$ + AS %6$sfunction%6$s begin - return query execute _query(' || quote_literal(_return_set_value) || '); + return query execute _query( %5$s ); end; - $function$;'; + %6$sfunction%6$s;', + _func_schema/*1*/, _func_name/*2*/, _func_args/*3*/, _func_result_type/*4*/, + _return_set_value/*5*/, '$'/*6*/); execute _mock_ddl; end if; if not _returns_set then - _mock_ddl = ' - create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' - RETURNS ' || _func_result_type || ' - LANGUAGE ' || _func_language || ' - AS $function$ - select ' || quote_nullable(_return_scalar_value) || '::' || pg_typeof(_return_scalar_value) || '; - $function$;'; + _mock_ddl = format(' + create or replace function %1$I.%2$I %3$s + RETURNS %4$s + LANGUAGE %5$s + AS %8$sfunction%8$s + select %6$L::%7$s; + %8$sfunction%8$s;', + _func_schema/*1*/, _func_name/*2*/, _func_args/*3*/, _func_result_type/*4*/, + _func_language/*5*/, _return_scalar_value/*6*/, pg_typeof(_return_scalar_value)/*7*/, '$'/*8*/); execute _mock_ddl; end if; -end $procedure$; - +end $function$; -CREATE OR REPLACE PROCEDURE fake_table( - _table_ident text[], +create or replace function fake_table( + _table_ident text[], _make_table_empty boolean default false, _leave_primary_key boolean default false, - _drop_not_null boolean DEFAULT false, + _drop_not_null boolean DEFAULT false, _drop_collation boolean DEFAULT false ) +returns void --It frees a table from any constraint (we call such a table as a fake) --faked table is a full copy of _table_name, but has no any constraint --without foreign and primary things you can do whatever you want in testing context LANGUAGE plpgsql -AS $procedure$ +AS $function$ declare _table record; _fk_table record; _fake_ddl text; _not_null_ddl text; begin - for _table in - select + for _table in + select quote_ident(coalesce((parse_ident(table_ident))[1], '')) table_schema, quote_ident(coalesce((parse_ident(table_ident))[2], '')) table_name, coalesce((parse_ident(table_ident))[1], '') table_schema_l, @@ -137,33 +123,33 @@ begin from unnest(_table_ident) as t(table_ident) loop - for _fk_table in + for _fk_table in -- collect all table's relations including primary key and unique constraint - select distinct * + select distinct * from ( - select + select fk_schema_name table_schema, fk_table_name table_name , fk_constraint_name constraint_name, false as is_pk, 1 as ord - from - pg_all_foreign_keys - where + from + pg_all_foreign_keys + where fk_schema_name = _table.table_schema_l and fk_table_name = _table.table_name_l union all - select + select fk_schema_name table_schema, fk_table_name table_name , fk_constraint_name constraint_name, false as is_pk, 1 as ord - from - pg_all_foreign_keys - where + from + pg_all_foreign_keys + where pk_schema_name = _table.table_schema_l and pk_table_name = _table.table_name_l union all - select + select table_schema, table_name , constraint_name , case when constraint_type = 'PRIMARY KEY' then true else false end as is_pk, 2 as ord - from + from information_schema.table_constraints - where + where table_schema = _table.table_schema_l and table_name = _table.table_name_l and constraint_type in ('PRIMARY KEY', 'UNIQUE') @@ -171,39 +157,40 @@ begin order by ord loop if not(_leave_primary_key and _fk_table.is_pk) then - _fake_ddl = 'alter table ' || _fk_table.table_schema || '.' || _fk_table.table_name || ' - drop constraint ' || _fk_table.constraint_name || ';'; + _fake_ddl = format('alter table %1$I.%2$I drop constraint %3$I;', + _fk_table.table_schema/*1*/, _fk_table.table_name/*2*/, _fk_table.constraint_name/*3*/ + ); execute _fake_ddl; end if; end loop; - + if _make_table_empty then - _fake_ddl = 'truncate table ' || _table.table_schema || '.' || _table.table_name || ';'; + _fake_ddl = format('truncate table %1$s.%2$s;', _table.table_schema, _table.table_name); execute _fake_ddl; end if; - + --Free table from not null constraints - _fake_ddl = 'alter table ' || _table.table_schema || '.' || _table.table_name || ' '; + _fake_ddl = format('alter table %1$s.%2$s ', _table.table_schema, _table.table_name); if _drop_not_null then - select - string_agg('alter column ' || t.attname || ' drop not null', ', ') + select + string_agg(format('alter column %1$I drop not null', t.attname), ', ') into _not_null_ddl - from - pg_catalog.pg_attribute t + from + pg_catalog.pg_attribute t where t.attrelid = (_table.table_schema || '.' || _table.table_name)::regclass and t.attnum > 0 and attnotnull; - + _fake_ddl = _fake_ddl || _not_null_ddl || ';'; else _fake_ddl = null; end if; - + if _fake_ddl is not null then execute _fake_ddl; end if; end loop; -end $procedure$; +end $function$; create or replace function call_count( _call_count int @@ -213,12 +200,12 @@ create or replace function call_count( RETURNS text LANGUAGE plpgsql AS $function$ -declare +declare _actual_call_count int; _track_functions_setting text; begin select current_setting('track_functions') into _track_functions_setting; - + if _track_functions_setting != 'all' then return fail('track_functions setting is not set. Must be all'); end if; @@ -252,9 +239,10 @@ $$ language plpgsql; -create or replace procedure print_table_as_json(in _table_schema text, in _table_name text) +create or replace function print_table_as_json(in _table_schema text, in _table_name text) +returns void language plpgsql -AS $procedure$ +AS $function$ declare _ddl text; _json text; @@ -263,14 +251,14 @@ declare --you can find the returned query in the output window in DBeaver, where we see raise notice command output --note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows begin - _ddl = ' + _ddl = format(' select json_agg( - array(select ' || quote_ident(_table_name) || ' from ' || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || ' limit 1000 - )) as j;'; + array(select %1$I from %2$I.%1$I limit 1000 + )) as j;', _table_name, _table_schema); execute _ddl into _json; _json = '[' || ltrim(rtrim(_json::text, ']'), '[') || ']'; - select string_agg(concat(c.column_name, ' ', case when lower(c.data_type) = 'array' then e.data_type || '[]' else c.data_type end), ', ') + select string_agg(concat(quote_ident(c.column_name), ' ', case when lower(c.data_type) = 'array' then e.data_type || '[]' else c.data_type end), ', ') into _columns from information_schema."columns" c left join information_schema.element_types e @@ -279,13 +267,15 @@ begin where c.table_schema = _table_schema and c.table_name = _table_name; - _json = $$select * from /*$$ || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || $$*/ json_to_recordset('$$ || _json || $$') as t($$ || _columns || $$)$$; + _json = format('select * from /*%1$I.%2$I*/ json_to_recordset(%3$L) as t(%4$s)', + _table_schema/*1*/, _table_name/*2*/, _json/*3*/, _columns/*4*/); raise notice '%', _json; -end $procedure$; +end $function$; -create or replace procedure print_query_as_json(in _prepared_statement_name text) +create or replace function print_query_as_json(in _prepared_statement_name text) +returns void language plpgsql -as $procedure$ +as $function$ declare _ddl text; _table_name text; @@ -294,9 +284,9 @@ declare --note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows begin _table_name = _prepared_statement_name || '_' || gen_random_uuid(); - _ddl = format('create table public.%1I as execute %2s', _table_name, _prepared_statement_name); + _ddl = format('create table public.%1$I as execute %2$s', _table_name, _prepared_statement_name); execute _ddl; - call print_table_as_json('public', _table_name::text); + perform print_table_as_json('public', _table_name::text); end; $procedure$; @@ -314,42 +304,6 @@ AS $function$ $function$ ; -CREATE OR REPLACE VIEW tap_funky - AS - SELECT - p.oid AS oid, - n.nspname AS schema, - p.proname AS name, - pg_catalog.pg_get_userbyid(p.proowner) AS owner, - proc_name.args AS args, - lower(coalesce( - proc_return."returns", - proc_return.sys_returns)) AS "returns", - p.prolang AS langoid, - p.proisstrict AS is_strict, - _prokind(p.oid) AS kind, - p.prosecdef AS is_definer, - p.proretset AS returns_set, - p.provolatile::char AS volatility, - pg_catalog.pg_function_is_visible(p.oid) AS is_visible, - l.lanname AS langname -FROM pg_catalog.pg_proc p -JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid -LEFT JOIN pg_language l ON l.oid = p.prolang -LEFT JOIN LATERAL ( - SELECT - (n.nspname::text || '.'::text) || p.proname::text AS qualified, - array_to_string(p.proargtypes::regtype[], ',') AS args -) proc_name ON true -LEFT JOIN LATERAL ( - SELECT - CASE - WHEN n.nspname != 'pg_catalog' - THEN pg_get_function_result((concat(proc_name.qualified, '(', proc_name.args, ')')::regprocedure)::oid) - ELSE NULL - END AS "returns", - CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END || p.prorettype::regtype AS sys_returns -) AS proc_return ON TRUE; -- index_is_partial( schema, table, index, description ) CREATE OR REPLACE FUNCTION index_is_partial ( NAME, NAME, NAME, text ) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 7e8178112..b9920cc13 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2603,24 +2603,42 @@ BEGIN END; $$ LANGUAGE plpgsql STABLE; -CREATE OR REPLACE VIEW tap_funky - AS SELECT p.oid AS oid, - n.nspname AS schema, - p.proname AS name, - pg_catalog.pg_get_userbyid(p.proowner) AS owner, - array_to_string(p.proargtypes::regtype[], ',') AS args, - CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END - || p.prorettype::regtype AS returns, - p.prolang AS langoid, - p.proisstrict AS is_strict, - _prokind(p.oid) AS kind, - p.prosecdef AS is_definer, - p.proretset AS returns_set, - p.provolatile::char AS volatility, - pg_catalog.pg_function_is_visible(p.oid) AS is_visible - FROM pg_catalog.pg_proc p - JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid -; +--added column "langname", "returns" to be used in mock_func function +CREATE OR REPLACE VIEW tap_funky AS +SELECT + p.oid AS oid, + n.nspname AS schema, + p.proname AS name, + pg_catalog.pg_get_userbyid(p.proowner) AS owner, + proc_name.args AS args, + lower(coalesce( + proc_return."returns", + proc_return.sys_returns)) AS "returns", + p.prolang AS langoid, + p.proisstrict AS is_strict, + _prokind(p.oid) AS kind, + p.prosecdef AS is_definer, + p.proretset AS returns_set, + p.provolatile::char AS volatility, + pg_catalog.pg_function_is_visible(p.oid) AS is_visible, + l.lanname AS langname +FROM pg_catalog.pg_proc p +JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid +LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang +LEFT JOIN LATERAL ( + select + format('%1I.%2I', n.nspname, p.proname) as qualified, + array_to_string(p.proargtypes::regtype[], ',') AS args +) proc_name ON true +LEFT JOIN LATERAL ( + SELECT + CASE + WHEN lower(n.nspname) != 'pg_catalog' + THEN pg_catalog.pg_get_function_result((concat(proc_name.qualified, '(', proc_name.args, ')')::regprocedure)::oid) + ELSE NULL + END AS "returns", + CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END || p.prorettype::regtype AS sys_returns +) AS proc_return ON TRUE; CREATE OR REPLACE FUNCTION _funkargs ( NAME[] ) RETURNS TEXT AS $$ @@ -11519,40 +11537,20 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; ---added a column "langname" used in mock_func function -CREATE OR REPLACE VIEW tap_funky - AS SELECT p.oid AS oid, - n.nspname AS schema, - p.proname AS name, - pg_catalog.pg_get_userbyid(p.proowner) AS owner, - array_to_string(p.proargtypes::regtype[], ',') AS args, - CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END - || p.prorettype::regtype AS returns, - p.prolang AS langoid, - p.proisstrict AS is_strict, - _prokind(p.oid) AS kind, - p.prosecdef AS is_definer, - p.proretset AS returns_set, - p.provolatile::char AS volatility, - pg_catalog.pg_function_is_visible(p.oid) AS is_visible, - l.lanname AS langname - FROM pg_catalog.pg_proc p - JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid - LEFT JOIN pg_language l ON l.oid = p.prolang -; ---this procedure creates a mock in place of a real function -create or replace procedure mock_func( +--this function creates a mock in place of a real function +create or replace function mock_func( _func_schema text , _func_name text , _func_args text , _return_set_value text default null , _return_scalar_value anyelement default null::text ) +returns void --creates a mock in place of a real function LANGUAGE plpgsql -AS $procedure$ -declare +AS $function$ +declare _mock_ddl text; _func_result_type text; _func_qualified_name text; @@ -11566,72 +11564,79 @@ begin and "name" = _func_name; if _func_language = 'sql' and _returns_set then - _mock_ddl = ' - create or replace function ' || quote_ident(_func_schema) || '.__' || quote_ident(_func_name) || '(_name text) - returns ' || _func_result_type || ' + _mock_ddl = format(' + create or replace function %1$I.__%2$I(_name text) + returns %3$s language plpgsql - AS $function$ + AS %5$sfunction%5$s begin - return query execute _query(' || quote_literal(_return_set_value) || '); + return query execute _query(%4$L); end; - $function$;'; + %5$sfunction%5$s;', + _func_schema/*1*/, _func_name/*2*/, _func_result_type/*3*/, _return_set_value/*4*/, '$'/*5*/); execute _mock_ddl; - _mock_ddl = ' - create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' - returns ' || _func_result_type || ' - language ' || _func_language || ' - AS $function$ - select * from ' || quote_ident(_func_schema) || '.__' || quote_ident(_func_name) || - '(' || quote_literal(_return_set_value) || '); - $function$;'; + _mock_ddl = format(' + create or replace function %1$I.%2$I %3$s + returns %4$s + language %5$s + AS %7$sfunction%7$s + select * from %1$I.__%2$I ( %6$L ); + %7$sfunction%7$s;', + _func_schema/*1*/, _func_name/*2*/, _func_args/*3*/, _func_result_type/*4*/, + _func_language/*5*/, _return_set_value/*6*/, '$'/*7*/); execute _mock_ddl; end if; - + if _func_language = 'plpgsql' and _returns_set then - _mock_ddl = ' - create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' - returns ' || _func_result_type || ' + _mock_ddl = format(' + create or replace function %1$I.%2$I %3$s + returns %4$s language plpgsql - AS $function$ + AS %6$sfunction%6$s begin - return query execute _query(' || quote_literal(_return_set_value) || '); + return query execute _query( %5$L ); end; - $function$;'; + %6$sfunction%6$s;', + _func_schema/*1*/, _func_name/*2*/, _func_args/*3*/, _func_result_type/*4*/, + _return_set_value/*5*/, '$'/*6*/); execute _mock_ddl; end if; if not _returns_set then - _mock_ddl = ' - create or replace function ' || quote_ident(_func_schema) || '.' || quote_ident(_func_name) || _func_args || ' - RETURNS ' || _func_result_type || ' - LANGUAGE ' || _func_language || ' - AS $function$ - select ' || quote_nullable(_return_scalar_value) || '::' || pg_typeof(_return_scalar_value) || '; - $function$;'; + _mock_ddl = format(' + create or replace function %1$I.%2$I %3$s + RETURNS %4$s + LANGUAGE %5$s + AS %8$sfunction%8$s + select %6$L::%7$s; + %8$sfunction%8$s;', + _func_schema/*1*/, _func_name/*2*/, _func_args/*3*/, _func_result_type/*4*/, + _func_language/*5*/, _return_scalar_value/*6*/, pg_typeof(_return_scalar_value)/*7*/, '$'/*8*/); execute _mock_ddl; end if; -end $procedure$; +end $function$; -CREATE OR REPLACE PROCEDURE fake_table( - _table_ident text[], +create or replace function fake_table( + _table_ident text[], _make_table_empty boolean default false, _leave_primary_key boolean default false, - _drop_not_null boolean DEFAULT false, + _drop_not_null boolean DEFAULT false, _drop_collation boolean DEFAULT false ) +returns void --It frees a table from any constraint (we call such a table as a fake) --faked table is a full copy of _table_name, but has no any constraint --without foreign and primary things you can do whatever you want in testing context LANGUAGE plpgsql -AS $procedure$ +AS $function$ declare _table record; _fk_table record; _fake_ddl text; _not_null_ddl text; begin - for _table in - select + for _table in + select quote_ident(coalesce((parse_ident(table_ident))[1], '')) table_schema, quote_ident(coalesce((parse_ident(table_ident))[2], '')) table_name, coalesce((parse_ident(table_ident))[1], '') table_schema_l, @@ -11639,33 +11644,33 @@ begin from unnest(_table_ident) as t(table_ident) loop - for _fk_table in + for _fk_table in -- collect all table's relations including primary key and unique constraint - select distinct * + select distinct * from ( - select + select fk_schema_name table_schema, fk_table_name table_name , fk_constraint_name constraint_name, false as is_pk, 1 as ord - from - pg_all_foreign_keys - where + from + pg_all_foreign_keys + where fk_schema_name = _table.table_schema_l and fk_table_name = _table.table_name_l union all - select + select fk_schema_name table_schema, fk_table_name table_name , fk_constraint_name constraint_name, false as is_pk, 1 as ord - from - pg_all_foreign_keys - where + from + pg_all_foreign_keys + where pk_schema_name = _table.table_schema_l and pk_table_name = _table.table_name_l union all - select + select table_schema, table_name , constraint_name , case when constraint_type = 'PRIMARY KEY' then true else false end as is_pk, 2 as ord - from + from information_schema.table_constraints - where + where table_schema = _table.table_schema_l and table_name = _table.table_name_l and constraint_type in ('PRIMARY KEY', 'UNIQUE') @@ -11673,39 +11678,40 @@ begin order by ord loop if not(_leave_primary_key and _fk_table.is_pk) then - _fake_ddl = 'alter table ' || _fk_table.table_schema || '.' || _fk_table.table_name || ' - drop constraint ' || _fk_table.constraint_name || ';'; + _fake_ddl = format('alter table %1$I.%2$I drop constraint %3$I;', + _fk_table.table_schema/*1*/, _fk_table.table_name/*2*/, _fk_table.constraint_name/*3*/ + ); execute _fake_ddl; end if; end loop; - + if _make_table_empty then - _fake_ddl = 'truncate table ' || _table.table_schema || '.' || _table.table_name || ';'; + _fake_ddl = format('truncate table %1$s.%2$s;', _table.table_schema, _table.table_name); execute _fake_ddl; end if; - + --Free table from not null constraints - _fake_ddl = 'alter table ' || _table.table_schema || '.' || _table.table_name || ' '; + _fake_ddl = format('alter table %1$s.%2$s ', _table.table_schema, _table.table_name); if _drop_not_null then - select - string_agg('alter column ' || t.attname || ' drop not null', ', ') + select + string_agg(format('alter column %1$I drop not null', t.attname), ', ') into _not_null_ddl - from - pg_catalog.pg_attribute t + from + pg_catalog.pg_attribute t where t.attrelid = (_table.table_schema || '.' || _table.table_name)::regclass and t.attnum > 0 and attnotnull; - + _fake_ddl = _fake_ddl || _not_null_ddl || ';'; else _fake_ddl = null; end if; - + if _fake_ddl is not null then execute _fake_ddl; end if; end loop; -end $procedure$; +end $function$; create or replace function call_count( _call_count int @@ -11715,12 +11721,12 @@ create or replace function call_count( RETURNS text LANGUAGE plpgsql AS $function$ -declare +declare _actual_call_count int; _track_functions_setting text; begin select current_setting('track_functions') into _track_functions_setting; - + if _track_functions_setting != 'all' then return fail('track_functions setting is not set. Must be all'); end if; @@ -11753,10 +11759,12 @@ end $$ language plpgsql; -create or replace procedure print_table_as_json(in _table_schema text, in _table_name text) + +create or replace function print_table_as_json(in _table_schema text, in _table_name text) +returns void language plpgsql -AS $procedure$ -declare +AS $function$ +declare _ddl text; _json text; _columns text; @@ -11764,14 +11772,14 @@ declare --you can find the returned query in the output window in DBeaver, where we see raise notice command output --note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows begin - _ddl = ' + _ddl = format(' select json_agg( - array(select ' || quote_ident(_table_name) || ' from ' || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || ' limit 1000 - )) as j;'; + array(select %1$I from %2$I.%1$I limit 1000 + )) as j;', _table_name, _table_schema); execute _ddl into _json; _json = '[' || ltrim(rtrim(_json::text, ']'), '[') || ']'; - select string_agg(concat(c.column_name, ' ', case when lower(c.data_type) = 'array' then e.data_type || '[]' else c.data_type end), ', ') + select string_agg(concat(quote_ident(c.column_name), ' ', case when lower(c.data_type) = 'array' then e.data_type || '[]' else c.data_type end), ', ') into _columns from information_schema."columns" c left join information_schema.element_types e @@ -11780,13 +11788,15 @@ begin where c.table_schema = _table_schema and c.table_name = _table_name; - _json = $$select * from /*$$ || quote_ident(_table_schema) || '.' || quote_ident(_table_name) || $$*/ json_to_recordset('$$ || _json || $$') as t($$ || _columns || $$)$$; + _json = format('select * from /*%1$I.%2$I*/ json_to_recordset(%3$L) as t(%4$s)', + _table_schema/*1*/, _table_name/*2*/, _json/*3*/, _columns/*4*/); raise notice '%', _json; -end $procedure$; +end $function$; -create or replace procedure print_query_as_json(in _prepared_statement_name text) +create or replace function print_query_as_json(in _prepared_statement_name text) +returns void language plpgsql -as $procedure$ +as $function$ declare _ddl text; _table_name text; @@ -11795,11 +11805,11 @@ declare --note! the returned dataset is limited to 1000 records. that's why you didn't get any jdbc error in dbeaver in case of huge amount of rows begin _table_name = _prepared_statement_name || '_' || gen_random_uuid(); - _ddl = format('create table public.%1I as execute %2s', _table_name, _prepared_statement_name); + _ddl = format('create table public.%1$I as execute %2$s', _table_name, _prepared_statement_name); execute _ddl; - call print_table_as_json('public', _table_name::text); + perform print_table_as_json('public', _table_name::text); end; -$procedure$; +$function$; CREATE OR REPLACE FUNCTION _get_func_oid(name, name, name[]) RETURNS oid @@ -11812,41 +11822,4 @@ AS $function$ AND args = _funkargs($3) AND is_visible $function$ -; - -CREATE OR REPLACE VIEW tap_funky - AS - SELECT - p.oid AS oid, - n.nspname AS schema, - p.proname AS name, - pg_catalog.pg_get_userbyid(p.proowner) AS owner, - proc_name.args AS args, - lower(coalesce( - proc_return."returns", - proc_return.sys_returns)) AS "returns", - p.prolang AS langoid, - p.proisstrict AS is_strict, - _prokind(p.oid) AS kind, - p.prosecdef AS is_definer, - p.proretset AS returns_set, - p.provolatile::char AS volatility, - pg_catalog.pg_function_is_visible(p.oid) AS is_visible, - l.lanname AS langname -FROM pg_catalog.pg_proc p -JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid -LEFT JOIN pg_language l ON l.oid = p.prolang -LEFT JOIN LATERAL ( - SELECT - (n.nspname::text || '.'::text) || p.proname::text AS qualified, - array_to_string(p.proargtypes::regtype[], ',') AS args -) proc_name ON true -LEFT JOIN LATERAL ( - SELECT - CASE - WHEN n.nspname != 'pg_catalog' - THEN pg_get_function_result((concat(proc_name.qualified, '(', proc_name.args, ')')::regprocedure)::oid) - ELSE NULL - END AS "returns", - CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END || p.prorettype::regtype AS sys_returns -) AS proc_return ON TRUE; \ No newline at end of file +; \ No newline at end of file diff --git a/test/sql/faketable.sql b/test/sql/faketable.sql index 8fbea7637..cadc5c1b9 100644 --- a/test/sql/faketable.sql +++ b/test/sql/faketable.sql @@ -53,13 +53,13 @@ RESET client_min_messages; CREATE FUNCTION test_faking_functionality() RETURNS SETOF TEXT AS $$ BEGIN - CALL fake_table( + perform fake_table( '{public.parent}'::text[], _make_table_empty => TRUE, _leave_primary_key => TRUE, _drop_not_null => FALSE); - CALL fake_table( + perform fake_table( '{public.child}'::text[], _make_table_empty => TRUE, _leave_primary_key => FALSE, diff --git a/test/sql/funcmock.sql b/test/sql/funcmock.sql index 85fde546e..f06e75795 100644 --- a/test/sql/funcmock.sql +++ b/test/sql/funcmock.sql @@ -42,7 +42,7 @@ DECLARE _mock_result time; BEGIN _hour_before = now() - INTERVAL '01:00'; - CALL mock_func('public', 'scalar_function', '()' + perform mock_func('public', 'scalar_function', '()' , _return_scalar_value => _hour_before::time); _mock_result = scalar_function(); @@ -52,17 +52,17 @@ BEGIN 'mock scalar_function'); PREPARE mock_set_sql_function AS SELECT * FROM (VALUES(1, 'x'), (2, 'z')) AS t(id, col) ORDER BY id; - CALL mock_func('public', 'set_sql_function', '()' + perform mock_func('public', 'set_sql_function', '()' , _return_set_value => 'mock_set_sql_function'); PREPARE returned_set_sql_function AS SELECT * FROM set_sql_function() ORDER BY id; - + RETURN query SELECT * FROM check_test( results_eq('returned_set_sql_function', 'mock_set_sql_function'), TRUE, 'mock sql function returning a set'); - + PREPARE mock_set_plpgsql_function AS SELECT * FROM (VALUES(1, 'w'), (2, 'q')) AS t(id, col) ORDER BY id; - CALL mock_func('public', 'set_plpgsql_function', '()' + perform mock_func('public', 'set_plpgsql_function', '()' , _return_set_value => 'mock_set_plpgsql_function'); PREPARE returned_set_plpgsql_function AS SELECT * FROM set_plpgsql_function() ORDER BY id; From a2af1dd4b7bbf9d02ddf4b7dbd4b5e02763244b4 Mon Sep 17 00:00:00 2001 From: mslava Date: Sun, 12 Oct 2025 22:01:11 +0700 Subject: [PATCH 15/20] Documentation on fake_table function --- doc/pgtap.mmd | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index f3ca45fc4..2c0f88422 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -8551,6 +8551,78 @@ table in a bunch of schemas: FROM (VALUES('schema1'), ('schema1')) AS stmp (sch) CROSS JOIN (VALUES('col_pk'), ('col2'), ('col3')) AS ctmp (col); +Mocking, faking and making your test independent +================================================ + +Faking +------ + +Let's say we have a main table and a subordinate table.. + + CREATE TABLE master( + some_id int primary key + ); + + CREATE TABLE child( + master_id int, + some_data numeric, + CONSTRAINT child_fk FOREIGN KEY(master_id) REFERENCES master(some_id) + ); + + --Your function simply sums up the values of the some_data column, filtering them by the master_id field. + + SELECT SUM(some_data) as sm FROM child WHERE master_id = $1; + +The main point here is that the master table is intended exclusively for user input, +and you cannot make any assumptions about the values stored in the some_id field. +Consequently, you will not be able to insert any data into the child table until +the corresponding data exists in the master table. + +What you can do is drop the foreign key constraint, insert some data into the child table only, +and perform any necessary checks. + +Please, do not worry that you've lost the relationship between tables and thus compromised relational integrity. +It's always well known which data are invalid for your system. Simply don't insert them. That's it. + +## `fake_table()` ### + + PERFORM fake_table( :_table_ident, :_make_table_empty, :_leave_primary_key, :_drop_not_null, :_drop_collation ); + PERFORM fake_table( :_table_ident, :_make_table_empty, :_leave_primary_key, :_drop_not_null ); + PERFORM fake_table( :_table_ident, :_make_table_empty, :_leave_primary_key ); + PERFORM fake_table( :_table_ident, :_make_table_empty ); + PERFORM fake_table( :_table_ident ); + +**Parameters** + +`:_table_ident` +: Text array of tables to be faked in the format 'my_schema.my_table'. Required. + +`:_make_table_empty` +: Boolean. If TRUE the desired tables would be truncated. Optional. FALSE by default. + +`:_leave_primary_key` +: Boolean. TRUE if you want primary key stay as is. Optional. FALSE by default. + +`:_drop_not_null` +: Boolean. TRUE if you want not null constraints stay as is. Optional. FALSE by default. + +`:_drop_collation` +: Boolean. Not yet implemented. Optional. FALSE by default. + +Sometimes a table contains a lot of junk data, especially in a development environment. +However, to ensure that your test runs on completely valid data, you clear the table before testing, +insert valid data, and then run the check. This is why the _make_table_empty parameter is useful. +If you follow the steps outlined above, you can be assured that your test does not rely on +junk data and can be reproduced at any time. Example. + + perform fake_table( + _table_ident => '{pgconf.account, pgconf.analytic, pgconf.osv, pgconf.transactions}'::text[], + _make_table_empty => true, + _leave_primary_key => false, + _drop_not_null => false, + _drop_collation => false + ); + Compose Yourself ================ From 6de5e8abba0b6e110e479efa673b2873b41eefe3 Mon Sep 17 00:00:00 2001 From: mslava Date: Sun, 19 Oct 2025 23:32:25 +0700 Subject: [PATCH 16/20] Documentation on mock_func function --- doc/pgtap.mmd | 184 +++++++++++++++++++++++++++++++++++- sql/pgtap--1.3.3--1.3.4.sql | 52 +++++++++- sql/pgtap.sql.in | 59 ++++++++++-- 3 files changed, 278 insertions(+), 17 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 2c0f88422..89e5ed96f 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -8554,10 +8554,12 @@ table in a bunch of schemas: Mocking, faking and making your test independent ================================================ +Examples of implementing mocking and faking functionality can be found in my fork [`on GitHub`](https://github.com/v-maliutin/pgtap/tree/examples_forking_and_mocking/examples). + Faking ------ -Let's say we have a main table and a subordinate table.. +Let's say we have a main table and a subordinate table. CREATE TABLE master( some_id int primary key @@ -8584,7 +8586,7 @@ and perform any necessary checks. Please, do not worry that you've lost the relationship between tables and thus compromised relational integrity. It's always well known which data are invalid for your system. Simply don't insert them. That's it. -## `fake_table()` ### +### `fake_table()` ### PERFORM fake_table( :_table_ident, :_make_table_empty, :_leave_primary_key, :_drop_not_null, :_drop_collation ); PERFORM fake_table( :_table_ident, :_make_table_empty, :_leave_primary_key, :_drop_not_null ); @@ -8598,7 +8600,7 @@ It's always well known which data are invalid for your system. Simply don't inse : Text array of tables to be faked in the format 'my_schema.my_table'. Required. `:_make_table_empty` -: Boolean. If TRUE the desired tables would be truncated. Optional. FALSE by default. +: Boolean. If TRUE the desired tables will be truncated. Optional. FALSE by default. `:_leave_primary_key` : Boolean. TRUE if you want primary key stay as is. Optional. FALSE by default. @@ -8623,6 +8625,182 @@ junk data and can be reproduced at any time. Example. _drop_collation => false ); + One note. The parameters _leave_primary_key and _drop_not_null are currently contradictory. + If you'd like to keep a primary key while making all columns nullable + (_leave_primary_key => true, _drop_not_null => true), you'll receive a runtime error. + This depends on whether the table truly has a primary key. I'm addressing this issue, but meanwhile, + the solution is to set _leave_primary_key => false, _drop_not_null => true, and then immediately + following the execution of 'fake_table', simply execute a command to create your primary key, + e.g., alter table my_table add constraint... + + In general, it's important to remember that there's no way to create a primary key consisting + of nullable columns. Furthermore, when creating a primary key across multiple columns, + PostgreSQL automatically enforces a NOT NULL constraint on each one. + +Mocking +------- + +Imagine a situation where you have one function that is called by another function. +The first function (the inner one) has complex logic and depends on multiple tables. +The second function (the outer one) simply performs a few actions using the result +returned by the first function. Should you populate tables required for the inner function +with test case data for every test scenario when testing the outer function? Goog news you shouldn't. +Simply create a mock for each test scenario and invoke it like a regular inner function. +The mock will bear the same name as your inner function, but will act instead of your real function. + + CREATE OR REPLACE FUNCTION pgconf.time_machine_now() + RETURNS time + LANGUAGE sql + AS $$ + SELECT now()::time; + $$; + + PERFORM mock_func('pgconf', 'time_machine_now', '()' + , _return_scalar_value => '13:00'::time); + +On the script above you can see how to create a mock for the 'time_machine_now()' function. +In production 'time_machine_now()' will return the current time. In test, if function has been mocked, +it will return 13:00 always. This is a scalar mock. + +Another mock you can do is a return set mock with sql string. + + CREATE OR REPLACE FUNCTION pgconf.time_machine_now() + RETURNS TABLE ( t time ) + LANGUAGE sql + AS $$ + SELECT now()::time; + $$; + + PERFORM tap.mock_func('pgconf', 'time_machine_now', '()' + , _return_set_value => 'select ''13:00''::time as t'); + + And my favorite case is + + PREPARE mock_time_machine_now AS SELECT '14:00'::time AS t; + PERFORM tap.mock_func('pgconf', 'time_machine_now', '()' + , _return_set_value => 'mock_time_machine_now'); + +### `mock_func()` ### + + PERFORM mock_func( :_func_schema, :_func_name, :_func_args, _return_scalar_value ); + PERFORM mock_func( :_func_schema, :_func_name, :_func_args, _return_set_value ); + PERFORM mock_func( :_func_schema, :_func_name, :_func_args ); + +**Parameters** + +`:_func_schema` +: Text. This is the schema where your function is declared. Required. + +`:_func_name` +: Text. Function name. Required. + +`:_func_args` +: Text. PG supports something similar to polymorphism. That’s why you always have to provide +a specific signature. This way PG will be able to find the function you want to mock. +If there are no parameters, just give '()'. +But if your function has some parameters, you must specify them as follows: +(_int_param int, _text_param text = null, _ts_param = now()). +The simplest way to find correct signature of your function is to call get_routine_signature. It is required. + +':_return_set_value' +: Text. Some scalar value that your mock have to return in test context. Optional. Default NULL. +Obviously, you have to provide either '_return_set_value' or '_return_scalar_value'. + +':_return_scalar_value' +: Text. Some SQL code forming a dataset that your mock should return in text context. +You may provide a name for a prepared statement. There is a convention to name a prepared statement +using the following pattern: 'mock_my_dataset'. Optional. Default NULL. +Obviously, you have to provide either '_return_set_value' or '_return_scalar_value'. + +### `get_routine_signature()` ### + + PERFORM get_routine_signature( :_routine_schema, :_routine_name ); + PERFORM get_routine_signature( :_routine_name ); + +**Parameters** + +`:_routine_schema` +: Text. This is the schema where your routine is declared. Required. + +`:_func_name` +: Text. Routine name. Required. + +The simple way to find out how PostgreSQL stores the signature of your routine. +Use this function to select at least the arguments with defaults ('args_with_defs') to pass this value to 'mock_func'. + +Assert to controls count of calls +--------------------------------- + +### `call_count()` ### + + SELECT call_count( :_call_count, :_func_schema, :_func_name, :_func_args ); + +To be able to gather a count on function calls, please ensure that the track_functions setting is set to 'all'. + +**Parameters** + +`:_call_count` +: Int. How many calls you are expecting. Required. + +`:_func_schema` +: Text. This is the schema where your function is declared. Required. + +`:_func_name` +: Text. Function name. Required. + +`:_func_args` +: Text. PG supports something similar to polymorphism. That’s why you always have to provide +a specific signature. This way PG will be able to find the function you want to mock. +If there are no parameters, just give '()'. +But if your function has some parameters, you must specify them as follows: +(_int_param int, _text_param text = null, _ts_param = now()). It is required. + +Helping functions +----------------- + +### `print_table_as_json()` ### + +The greatest feature of pgTap is that every test run happens within its own transaction. That's awesome, +but it complicates tracking what's happening internally. Therefore, by calling the 'print_table_as_json' +function, you'll be able to see what data was present in a table (or query) at the time the test was running. + +**Parameters** + +`:_table_schema` +: Text. This is the schema where your table is declared. Required. + +`:_table_name` +: Text. Table name. Required. + +In the current version, all we need to do is create a table using some SQL statements in a certain schema +and pass the table and schema names to the function. The function will then issue a 'RAISE NOTICE' command +containing SQL code that you can copy-paste and execute. The result will appear as a regular table. +If you work in DBeaver look result of 'RAISE NOTICE' command in Output window. + +### `print_query_as_json()` ### + +It performs precisely the same action as 'print_table_as_json', except you don't have to create a table. +Simply prepare a statement and supply its name to the function. +It's a great idea that you can inspect any data you're working with during testing. +Simply construct a query returning your variables, settings, or any other relevant information, +and output them via the 'print_query_as_json' function. + +**Parameters** + +`:_statements` +: Text. This is the name of the prepared statement whose data you're looking to explore. Required. + +### `drop_prepared_statement()` ### + +Any prepared statement is a session-level object. Thus, if you reuse the same names across tests, +similar to my approach, you must remove previously created prepared statements before you can use it again. +It's advisable to leverage the 'setup' and 'teardown' features provided by pgTap. + +**Parameters** + +`:_statements` +: Array of text. Those are names of the prepared statements you want to drop. Required. + Compose Yourself ================ diff --git a/sql/pgtap--1.3.3--1.3.4.sql b/sql/pgtap--1.3.3--1.3.4.sql index 00b807d25..fda128dfa 100644 --- a/sql/pgtap--1.3.3--1.3.4.sql +++ b/sql/pgtap--1.3.3--1.3.4.sql @@ -16,6 +16,31 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; +CREATE OR REPLACE FUNCTION get_routine_signature( + _routine_schema name + , _routine_name name) + RETURNS TABLE (routine_schema TEXT, routine_name TEXT, routine_params TEXT) + LANGUAGE SQL STABLE +AS $function$ + SELECT + "schema", "name", args_with_defs + FROM tap_funky + WHERE + "schema" = _func_schema and + "name" = _func_name; +$function$; + +CREATE OR REPLACE FUNCTION get_routine_signature( + _routine_name name) + RETURNS TABLE (routine_schema TEXT, routine_name TEXT, routine_params TEXT) + LANGUAGE SQL STABLE +AS $function$ + SELECT + "schema", "name", args_with_defs + FROM tap_funky + WHERE + "name" = _func_name; +$function$; --this function creates a mock in place of a real function create or replace function mock_func( @@ -35,13 +60,30 @@ declare _func_qualified_name text; _func_language text; _returns_set bool; + _variants text; + _ex_msg text; begin - select "returns", langname, returns_set - into _func_result_type, _func_language, _returns_set - from tap_funky - where "schema" = _func_schema - and "name" = _func_name; + --First of all, we have to identify which function we must mock. If there is no such function, throw an error. + begin + select "returns", langname, returns_set + into strict _func_result_type, _func_language, _returns_set + from tap_funky + where "schema" = _func_schema + and "name" = _func_name + and args_with_defs = _func_args; + EXCEPTION WHEN NO_DATA_FOUND OR TOO_MANY_ROWS THEN + select string_agg(E'\t - ' || format('%I.%I %s', "schema", "name", args_with_defs), E'\n')::text + into _variants + from tap_funky + where "name" = _func_name; + _ex_msg = format('Routine %I.%I %s does not exist.', + _func_schema, _func_name, _func_args) || E'\n' || 'Possible variants are:' || E'\n' || _variants; + RAISE EXCEPTION '%', _ex_msg; + end; + --This is the case when we need to mock a function written in SQL. + --But in order to be able to execute the mocking functionality, we need to have a function written in plpgsql. + --That is why we create a hidden function which name starts with "__". if _func_language = 'sql' and _returns_set then _mock_ddl = format(' create or replace function %1$I.__%2$I(_name text) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index b9920cc13..fab08cb42 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2603,17 +2603,50 @@ BEGIN END; $$ LANGUAGE plpgsql STABLE; ---added column "langname", "returns" to be used in mock_func function +CREATE OR REPLACE FUNCTION _routineresult ( _routine_signature text ) +RETURNS TEXT AS $$ +BEGIN + /* + We need this function because without handling errors we will run into an error like + SQL Error [42883]: ERROR: function + "pg_toast.mock_func(text,text,text,text,anyelement)" dose not exist + It happens when we write filter 'routine_return.returns is not null' in tap_funky view. + Handling 'undefined_object' does not work + */ + RETURN pg_catalog.pg_get_function_result((_routine_signature::regprocedure)::oid); +EXCEPTION WHEN others THEN + RETURN NULL; +END; +$$ LANGUAGE PLPGSQL STABLE; + +CREATE OR REPLACE FUNCTION _routineargsdefs ( _routine_signature text ) +RETURNS TEXT AS $$ +BEGIN + /* + We need this function because without handling errors we will run into an error like + SQL Error [42883]: ERROR: function + "pg_toast.mock_func(text,text,text,text,anyelement)" dose not exist + It happens when we write filter 'routine_args.with_defs is not null' in tap_funky view. + Handling 'undefined_object' does not work + */ + RETURN pg_catalog.pg_get_function_arguments((_routine_signature::regprocedure)::oid); +EXCEPTION WHEN others THEN + RETURN NULL; +END; +$$ LANGUAGE PLPGSQL STABLE; + + +--added columns "langname", "returns" and args_with_defs to be used in mock_func function CREATE OR REPLACE VIEW tap_funky AS SELECT p.oid AS oid, n.nspname AS schema, p.proname AS name, pg_catalog.pg_get_userbyid(p.proowner) AS owner, - proc_name.args AS args, + routine_name.args AS args, lower(coalesce( - proc_return."returns", - proc_return.sys_returns)) AS "returns", + routine_info."returns", + routine_info.sys_returns)) AS "returns", p.prolang AS langoid, p.proisstrict AS is_strict, _prokind(p.oid) AS kind, @@ -2621,7 +2654,10 @@ SELECT p.proretset AS returns_set, p.provolatile::char AS volatility, pg_catalog.pg_function_is_visible(p.oid) AS is_visible, - l.lanname AS langname + l.lanname AS langname, + format('(%s)', + routine_info.args_with_defs + ) AS args_with_defs FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang @@ -2629,16 +2665,21 @@ LEFT JOIN LATERAL ( select format('%1I.%2I', n.nspname, p.proname) as qualified, array_to_string(p.proargtypes::regtype[], ',') AS args -) proc_name ON true +) routine_name ON true LEFT JOIN LATERAL ( SELECT CASE WHEN lower(n.nspname) != 'pg_catalog' - THEN pg_catalog.pg_get_function_result((concat(proc_name.qualified, '(', proc_name.args, ')')::regprocedure)::oid) + THEN _routineresult(concat(routine_name.qualified, '(', routine_name.args, ')')) + ELSE NULL + END collate "default" AS "returns", + CASE + WHEN lower(n.nspname) != 'pg_catalog' + THEN _routineargsdefs(concat(routine_name.qualified, '(', routine_name.args, ')')) ELSE NULL - END AS "returns", + end collate "default" AS "args_with_defs", CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END || p.prorettype::regtype AS sys_returns -) AS proc_return ON TRUE; +) AS routine_info ON TRUE; CREATE OR REPLACE FUNCTION _funkargs ( NAME[] ) RETURNS TEXT AS $$ From edf0f9af2abfd7bbaae6e4885b5ad5d3e6ce31f3 Mon Sep 17 00:00:00 2001 From: mslava Date: Mon, 20 Oct 2025 21:35:14 +0700 Subject: [PATCH 17/20] Updated pgtap.sql.in --- examples/mocking_and_faking.sql | 359 -------------------------------- sql/pgtap--1.3.3--1.3.4.sql | 6 +- sql/pgtap.sql.in | 61 +++++- 3 files changed, 54 insertions(+), 372 deletions(-) delete mode 100644 examples/mocking_and_faking.sql diff --git a/examples/mocking_and_faking.sql b/examples/mocking_and_faking.sql deleted file mode 100644 index 3d070adcc..000000000 --- a/examples/mocking_and_faking.sql +++ /dev/null @@ -1,359 +0,0 @@ -/* -DROP EXTENSION pgtap; - -CREATE EXTENSION pgtap schema tap; -*/ - -create schema if not exists tap; -create schema if not exists pgconf; -create schema if not exists tests; - -set search_path to public, tap, pgconf; -set track_functions = 'all' ; - -drop function if exists pgconf.get_osv_slice(int, int, int); -drop function if exists pgconf.get_tree_of(int); -drop function if exists pgconf.time_machine_now(); -drop procedure if exists pgconf.make_osv_report(); -drop procedure if exists tests.create_test_data(); -drop function if exists tests.test_osv_on_time(); -drop function if exists tests.test_osv_not_on_time(); -drop function if exists tests.test_get_tree_of_called_times(); - -drop table if exists pgconf.osv; -drop table if exists pgconf.transactions; -drop table if exists pgconf.analytic; -drop table if exists pgconf.account; - -create table pgconf.account( - id int generated always as identity primary key - , parent_id int - , num text - , constraint account_01_fk foreign key(parent_id) references pgconf.account(id) -); - -create table pgconf.analytic( - id int generated always as identity primary key - , subconto text -); - -create table pgconf.transactions( - account_num text - , subconto_1 text - , subconto_2 text - , subconto_3 text - , amount_dt numeric(15, 2) - , amount_ct numeric(15, 2) -); - -create table pgconf.osv( - account_id int - , subconto_1_id int - , subconto_2_id int - , subconto_3_id int - , amount_dt numeric(15, 2) - , amount_ct numeric(15, 2) - , constraint osv_01_fk foreign key(account_id) references pgconf.account(id) - , constraint osv_02_fk foreign key(subconto_1_id) references pgconf.analytic(id) - , constraint osv_03_fk foreign key(subconto_2_id) references pgconf.analytic(id) - , constraint osv_04_fk foreign key(subconto_3_id) references pgconf.analytic(id) -); - -create or replace function pgconf.get_osv_slice(_account_id int, _subc_1 int, _subc_2 int) -returns table( - ordercol text[] - , account_num text - , subconto_1_id int - , subconto_1 text - , subconto_2_id int - , subconto_2 text - , subconto_3 text - , amount_dt numeric(15, 2) - , amount_ct numeric(15, 2) -) -language plpgsql -as $$ -begin -return query -with recursive account_tree as ( - select id, parent_id, num - from pgconf.account - where (id = _account_id or _account_id is null) - and parent_id is null - union all - select a.id, a.parent_id, a.num - from account_tree t - join pgconf.account a - on a.parent_id = t.id -)search depth first by num set account_order -select - t.account_order::text[] - , t.num as account_num - , a1.id as subconto_1_id - , a1.subconto as subconto_1 - , a2.id as subconto_2_id - , a2.subconto as subconto_2 - , a3.subconto as subconto_3 - , o.amount_dt - , o.amount_ct -from account_tree t -left join pgconf.osv o -on o.account_id = t.id -left join pgconf.analytic a1 -on a1.id = o.subconto_1_id -left join pgconf.analytic a2 -on a2.id = o.subconto_2_id -left join pgconf.analytic a3 -on a3.id = o.subconto_3_id -where - pgconf.time_machine_now() between '12:00'::time and '15:00'::time - and (_account_id is null and o.subconto_1_id is null and o.subconto_2_id is null and o.subconto_3_id is null) -order by t.account_order -; -end; -$$; - -create or replace function pgconf.get_tree_of(_account_id int) - returns table(id int, parent_id int, num text, lev int, is_folder bool) - language sql -as $function$ -with recursive acc as ( - select id, parent_id, num, 1 as l, True as is_folder from pgconf.account - where (id = _account_id and _account_id is not null) or (_account_id is null and parent_id is null) - union all - select a2.id, a2.parent_id, a2.num, a1.l + 1 as l - , exists(select from pgconf.account ca where ca.parent_id = a2.id) as is_folder - from acc a1 - join pgconf.account a2 - on a1.id = a2.parent_id -) -select * from acc a -$function$ -; - -create or replace function pgconf.time_machine_now() -returns time -language sql -as $$ - select now()::time; -$$; - -create or replace procedure pgconf.make_osv_report() -language plpgsql -as $$ -begin - insert into pgconf.osv(account_id, subconto_1_id, subconto_2_id, subconto_3_id, amount_dt, amount_ct) - select a.id, a1.id, a2.id, a3.id, sum(amount_dt), sum(amount_ct) - from pgconf.account a - join pgconf.transactions t - on t.account_num = a.num - left join pgconf.analytic a1 - on a1.subconto = t.subconto_1 - left join pgconf.analytic a2 - on a2.subconto = t.subconto_2 - left join pgconf.analytic a3 - on a3.subconto = t.subconto_3 - group by a.id, grouping sets( - (a.id, a1.id, a2.id, a3.id) - , (a.id, a1.id, a2.id) - , (a.id, a1.id) - , (a.id) - ); - - insert into pgconf.osv(account_id, subconto_1_id, subconto_2_id, subconto_3_id, amount_dt, amount_ct) - select - acc.id - , null, null, null - , agg.amount_dt - , agg.amount_ct - from pgconf.get_tree_of(null) as acc - left join lateral( - select - sum(osv.amount_dt) as amount_dt - , sum(osv.amount_ct) as amount_ct - from pgconf.osv - where osv.account_id in (select id from pgconf.get_tree_of(acc.id) t where not t.is_folder) - and subconto_1_id is null and subconto_2_id is null and subconto_3_id is null - ) as agg on true - where - acc.is_folder; -end; -$$; - -create or replace procedure tests.create_test_data() -language plpgsql -as $$ -begin - perform tap.fake_table( - _table_ident => '{pgconf.account, pgconf.analytic, pgconf.osv, pgconf.transactions}'::text[], - _make_table_empty => true, - _leave_primary_key => false, - _drop_not_null => false, - _drop_collation => false - ); - - insert into pgconf.account(parent_id, num) values(null, '02'); - - insert into pgconf.account(parent_id, num) - select id, '02.01' - from pgconf.account where num = '02'; - - insert into pgconf.account(parent_id, num) values(null, '01'); - - insert into pgconf.account(parent_id, num) - select id, '03.01' - from pgconf.account where num = '01'; - - insert into pgconf.account(parent_id, num) - select id, '01.01.01' - from pgconf.account where num = '03.01'; - - insert into pgconf.account(parent_id, num) - select id, '01.01.02' - from pgconf.account where num = '03.01'; - - insert into pgconf.account(parent_id, num) - select id, '01.01.01.01' - from pgconf.account where num = '01.01.01'; - - insert into pgconf.account(parent_id, num) - select id, '01.01.01.02' - from pgconf.account where num = '01.01.01'; - - insert into pgconf.account(parent_id, num) - select id, '01.01.02.01' - from pgconf.account where num = '01.01.02'; - - insert into pgconf.analytic(subconto) values('Суб_1'), ('Суб_2'), ('Суб_3'), ('Суб_4'), ('Суб_5'), ('Суб_6'), ('Суб_7'); - - insert into pgconf.transactions(account_num, subconto_1, subconto_2, subconto_3, amount_dt, amount_ct) values - ('01.01.01.01', 'Суб_1', 'Суб_2', 'Суб_4', 10, 0) - , ('01.01.01.01', 'Суб_1', 'Суб_2', 'Суб_5', 10, 0) - , ('01.01.01.01', 'Суб_1', 'Суб_3', 'Суб_6', 10, 0) - , ('01.01.01.02', 'Суб_1', 'Суб_3', 'Суб_7', 10, 0) - , ('01.01.02.01', 'Суб_1', 'Суб_2', 'Суб_4', 10, 0) - , ('02.01', 'Суб_1', 'Суб_3', 'Суб_5', 0, 10) - , ('02.01', 'Суб_1', 'Суб_2', 'Суб_4', 0, 10); - - call pgconf.make_osv_report(); -end; -$$; - -create or replace function tests.test_osv_ordered_in_depth() -returns setof text -language plpgsql -as $$ -begin - -- GIVEN - call tests.create_test_data(); - perform tap.mock_func('pgconf', 'time_machine_now', '()' - , _return_scalar_value => '13:00'::time); - - -- WHEN - perform tap.drop_prepared_statement('{expected, returned}'::text[]); - - prepare expected as - select num::text - from (values ('01', 1), ('03.01', 2), ('01.01.01', 2), ('01.01.01.01', 3), ('01.01.01.02', 4) - , ('01.01.02', 5), ('01.01.02.01', 6) - , ('02', 7), ('02.01', 8)) as t(num, id) - order by id; - - prepare returned as - select account_num from pgconf.get_osv_slice(null, null, null); - - -- THEN - return query - select tap.results_eq( - 'returned', - 'expected', - 'Accounts must be sorted in depth first.' - ); - - - create table pgconf.slice as - select * from pgconf.get_osv_slice(null, null, null); - - perform tap.print_table_as_json('pgconf', 'slice'); - perform tap.print_table_as_json('pgconf', 'account'); - - -- WHEN - perform tap.drop_prepared_statement('{expected, returned}'::text[]); -end; -$$; - -create or replace function tests.test_osv_on_time() -returns setof text -language plpgsql -as $$ -begin - -- GIVEN - call tests.create_test_data(); - perform tap.mock_func('pgconf', 'time_machine_now', '()' - , _return_scalar_value => '15:01'::time); - - create table pgconf.x as select * from pgconf.time_machine_now(); - perform tap.print_table_as_json('pgconf', 'x'); - - -- WHEN - perform tap.drop_prepared_statement('{returned}'::text[]); - - prepare returned as - select * from pgconf.get_osv_slice(null, null, null); - - -- THEN - return query - select tap.is_empty( - 'returned', - 'It is not good time to make osv report.' - ); - - perform tap.drop_prepared_statement('{returned}'::text[]); -end; -$$; - -create or replace function tests.test_osv_not_on_time() -returns setof text -language plpgsql -as $$ -begin - -- GIVEN - call tests.create_test_data(); - perform tap.mock_func('pgconf', 'time_machine_now', '()' - , _return_set_value => null::text, _return_scalar_value => '13:00'::time); - - -- WHEN - perform tap.drop_prepared_statement('{returned}'::text[]); - - prepare returned as - select * from pgconf.get_osv_slice(null, null, null); - - -- THEN - return query - select tap.isnt_empty( - 'returned', - 'Time has come. We can make osv report.' - ); - - perform tap.drop_prepared_statement('{expected}'::text[]); -end; -$$; - -create or replace function tests.test_get_tree_of_called_times() -returns setof text -language plpgsql -as $$ -begin - -- GIVEN - call tests.create_test_data(); - - -- THEN - return query - select tap.call_count(6, 'pgconf' - , 'get_tree_of', '{int}'::name[]); -end; -$$; - -select * from tap.runtests('tests', '^test_'); - -select * from tap.runtests('tests', 'test_osv_on_time'); \ No newline at end of file diff --git a/sql/pgtap--1.3.3--1.3.4.sql b/sql/pgtap--1.3.3--1.3.4.sql index fda128dfa..a330d21e1 100644 --- a/sql/pgtap--1.3.3--1.3.4.sql +++ b/sql/pgtap--1.3.3--1.3.4.sql @@ -71,14 +71,14 @@ begin where "schema" = _func_schema and "name" = _func_name and args_with_defs = _func_args; - EXCEPTION WHEN NO_DATA_FOUND OR TOO_MANY_ROWS THEN + exception when NO_DATA_FOUND or TOO_MANY_ROWS then select string_agg(E'\t - ' || format('%I.%I %s', "schema", "name", args_with_defs), E'\n')::text into _variants from tap_funky where "name" = _func_name; _ex_msg = format('Routine %I.%I %s does not exist.', _func_schema, _func_name, _func_args) || E'\n' || 'Possible variants are:' || E'\n' || _variants; - RAISE EXCEPTION '%', _ex_msg; + raise exception '%', _ex_msg; end; --This is the case when we need to mock a function written in SQL. @@ -330,7 +330,7 @@ begin execute _ddl; perform print_table_as_json('public', _table_name::text); end; -$procedure$; +$function$; CREATE OR REPLACE FUNCTION _get_func_oid(name, name, name[]) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index fab08cb42..238dc04ae 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -11578,7 +11578,32 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; - +CREATE OR REPLACE FUNCTION get_routine_signature( + _routine_schema name + , _routine_name name) + RETURNS TABLE (routine_schema TEXT, routine_name TEXT, routine_params TEXT) + LANGUAGE SQL STABLE +AS $function$ + SELECT + "schema", "name", args_with_defs + FROM tap_funky + WHERE + "schema" = _func_schema and + "name" = _func_name; +$function$; + +CREATE OR REPLACE FUNCTION get_routine_signature( + _routine_name name) + RETURNS TABLE (routine_schema TEXT, routine_name TEXT, routine_params TEXT) + LANGUAGE SQL STABLE +AS $function$ + SELECT + "schema", "name", args_with_defs + FROM tap_funky + WHERE + "name" = _func_name; +$function$; + --this function creates a mock in place of a real function create or replace function mock_func( _func_schema text @@ -11597,13 +11622,30 @@ declare _func_qualified_name text; _func_language text; _returns_set bool; + _variants text; + _ex_msg text; begin - select "returns", langname, returns_set - into _func_result_type, _func_language, _returns_set - from tap_funky - where "schema" = _func_schema - and "name" = _func_name; - + --First of all, we have to identify which function we must mock. If there is no such function, throw an error. + begin + select "returns", langname, returns_set + into strict _func_result_type, _func_language, _returns_set + from tap_funky + where "schema" = _func_schema + and "name" = _func_name + and args_with_defs = _func_args; + exception when NO_DATA_FOUND or TOO_MANY_ROWS then + select string_agg(E'\t - ' || format('%I.%I %s', "schema", "name", args_with_defs), E'\n')::text + into _variants + from tap_funky + where "name" = _func_name; + _ex_msg = format('Routine %I.%I %s does not exist.', + _func_schema, _func_name, _func_args) || E'\n' || 'Possible variants are:' || E'\n' || _variants; + raise exception '%', _ex_msg; + end; + + --This is the case when we need to mock a function written in SQL. + --But in order to be able to execute the mocking functionality, we need to have a function written in plpgsql. + --That is why we create a hidden function which name starts with "__". if _func_language = 'sql' and _returns_set then _mock_ddl = format(' create or replace function %1$I.__%2$I(_name text) @@ -11621,7 +11663,7 @@ begin returns %4$s language %5$s AS %7$sfunction%7$s - select * from %1$I.__%2$I ( %6$L ); + select * from %1$I.__%2$I ( %6$s ); %7$sfunction%7$s;', _func_schema/*1*/, _func_name/*2*/, _func_args/*3*/, _func_result_type/*4*/, _func_language/*5*/, _return_set_value/*6*/, '$'/*7*/); @@ -11635,7 +11677,7 @@ begin language plpgsql AS %6$sfunction%6$s begin - return query execute _query( %5$L ); + return query execute _query( %5$s ); end; %6$sfunction%6$s;', _func_schema/*1*/, _func_name/*2*/, _func_args/*3*/, _func_result_type/*4*/, @@ -11800,7 +11842,6 @@ end $$ language plpgsql; - create or replace function print_table_as_json(in _table_schema text, in _table_name text) returns void language plpgsql From 2273ba276856a059f322c95235552d95daf69247 Mon Sep 17 00:00:00 2001 From: mslava Date: Tue, 21 Oct 2025 21:41:02 +0700 Subject: [PATCH 18/20] Add ability to mock a view + some fixes --- sql/pgtap--1.3.3--1.3.4.sql | 50 ++++++++++++++++++++++++++++++++---- sql/pgtap.sql.in | 51 +++++++++++++++++++++++++++++++++---- 2 files changed, 91 insertions(+), 10 deletions(-) diff --git a/sql/pgtap--1.3.3--1.3.4.sql b/sql/pgtap--1.3.3--1.3.4.sql index a330d21e1..443ea54f9 100644 --- a/sql/pgtap--1.3.3--1.3.4.sql +++ b/sql/pgtap--1.3.3--1.3.4.sql @@ -26,8 +26,8 @@ AS $function$ "schema", "name", args_with_defs FROM tap_funky WHERE - "schema" = _func_schema and - "name" = _func_name; + "schema" = _routine_schema and + "name" = _routine_name; $function$; CREATE OR REPLACE FUNCTION get_routine_signature( @@ -39,7 +39,7 @@ AS $function$ "schema", "name", args_with_defs FROM tap_funky WHERE - "name" = _func_name; + "name" = _routine_name; $function$; --this function creates a mock in place of a real function @@ -101,7 +101,7 @@ begin returns %4$s language %5$s AS %7$sfunction%7$s - select * from %1$I.__%2$I ( %6$s ); + select * from %1$I.__%2$I ( ''%6$s'' ); %7$sfunction%7$s;', _func_schema/*1*/, _func_name/*2*/, _func_args/*3*/, _func_result_type/*4*/, _func_language/*5*/, _return_set_value/*6*/, '$'/*7*/); @@ -137,12 +137,35 @@ begin end if; end $function$; +--This function creates a mock in place of a real view +create or replace function mock_view( + _view_schema text + , _view_name text + , _return_set_sql text default null +) +--Create a mock in place of a real view +language plpgsql +as $function$ +declare + _mock_ddl text; + _func_result_type text; + _func_qualified_name text; + _func_language text; + _returns_set bool; +begin + _mock_ddl = format('drop view %I.%I', _view_schema, _view_name); + execute _mock_ddl; + _mock_ddl = format('create view %I.%I as %s', _view_schema, _view_name, _return_set_value); + execute _mock_ddl; +end; $function$; + create or replace function fake_table( _table_ident text[], _make_table_empty boolean default false, _leave_primary_key boolean default false, _drop_not_null boolean DEFAULT false, _drop_collation boolean DEFAULT false + _drop_partitions boolean DEFAULT false ) returns void --It frees a table from any constraint (we call such a table as a fake) @@ -153,6 +176,7 @@ AS $function$ declare _table record; _fk_table record; + _part_table record; _fake_ddl text; _not_null_ddl text; begin @@ -221,7 +245,16 @@ begin from pg_catalog.pg_attribute t where t.attrelid = (_table.table_schema || '.' || _table.table_name)::regclass - and t.attnum > 0 and attnotnull; + and t.attnum > 0 and attnotnull + and ( + ( + --and the column is not part of PK when we wat ot leave PK as is + t.attname::text != all((select _keys(_table.table_schema, _table.table_name, 'p'))::text[]) and + _leave_primary_key + ) or + --we can drop an NOT NULL constraint as there is not PK already + not _leave_primary_key + ); _fake_ddl = _fake_ddl || _not_null_ddl || ';'; else @@ -231,6 +264,13 @@ begin if _fake_ddl is not null then execute _fake_ddl; end if; + + if _drop_partitions then + for _part_table in select _parts from _parts(_table.table_schema, _table.table_name) loop + _fake_ddl = 'drop table if exists ' || _part_table._parts || ';'; + execute _fake_ddl; + end loop; + end if; end loop; end $function$; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 238dc04ae..cb7434ad4 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -11588,8 +11588,8 @@ AS $function$ "schema", "name", args_with_defs FROM tap_funky WHERE - "schema" = _func_schema and - "name" = _func_name; + "schema" = _routine_schema and + "name" = _routine_name; $function$; CREATE OR REPLACE FUNCTION get_routine_signature( @@ -11601,7 +11601,7 @@ AS $function$ "schema", "name", args_with_defs FROM tap_funky WHERE - "name" = _func_name; + "name" = _routine_name; $function$; --this function creates a mock in place of a real function @@ -11663,7 +11663,7 @@ begin returns %4$s language %5$s AS %7$sfunction%7$s - select * from %1$I.__%2$I ( %6$s ); + select * from %1$I.__%2$I ( ''%6$s'' ); %7$sfunction%7$s;', _func_schema/*1*/, _func_name/*2*/, _func_args/*3*/, _func_result_type/*4*/, _func_language/*5*/, _return_set_value/*6*/, '$'/*7*/); @@ -11699,12 +11699,35 @@ begin end if; end $function$; +--This function creates a mock in place of a real view +create or replace function mock_view( + _view_schema text + , _view_name text + , _return_set_sql text default null +) +--Create a mock in place of a real view +language plpgsql +as $function$ +declare + _mock_ddl text; + _func_result_type text; + _func_qualified_name text; + _func_language text; + _returns_set bool; +begin + _mock_ddl = format('drop view %I.%I', _view_schema, _view_name); + execute _mock_ddl; + _mock_ddl = format('create view %I.%I as %s', _view_schema, _view_name, _return_set_value); + execute _mock_ddl; +end; $function$; + create or replace function fake_table( _table_ident text[], _make_table_empty boolean default false, _leave_primary_key boolean default false, _drop_not_null boolean DEFAULT false, _drop_collation boolean DEFAULT false + _drop_partitions boolean DEFAULT false ) returns void --It frees a table from any constraint (we call such a table as a fake) @@ -11715,6 +11738,7 @@ AS $function$ declare _table record; _fk_table record; + _part_table record; _fake_ddl text; _not_null_ddl text; begin @@ -11783,7 +11807,16 @@ begin from pg_catalog.pg_attribute t where t.attrelid = (_table.table_schema || '.' || _table.table_name)::regclass - and t.attnum > 0 and attnotnull; + and t.attnum > 0 and attnotnull + and ( + ( + --and the column is not part of PK when we wat ot leave PK as is + t.attname::text != all((select _keys(_table.table_schema, _table.table_name, 'p'))::text[]) and + _leave_primary_key + ) or + --we can drop an NOT NULL constraint as there is not PK already + not _leave_primary_key + ); _fake_ddl = _fake_ddl || _not_null_ddl || ';'; else @@ -11793,6 +11826,13 @@ begin if _fake_ddl is not null then execute _fake_ddl; end if; + + if _drop_partitions then + for _part_table in select _parts from _parts(_table.table_schema, _table.table_name) loop + _fake_ddl = 'drop table if exists ' || _part_table._parts || ';'; + execute _fake_ddl; + end loop; + end if; end loop; end $function$; @@ -11842,6 +11882,7 @@ end $$ language plpgsql; + create or replace function print_table_as_json(in _table_schema text, in _table_name text) returns void language plpgsql From 4ae191bfe935248acbae4e0d8b34a5f0db525514 Mon Sep 17 00:00:00 2001 From: mslava Date: Fri, 24 Oct 2025 15:14:17 +0700 Subject: [PATCH 19/20] Fixes, new tests, new documentation --- doc/pgtap.mmd | 26 ++++++++++++- sql/pgtap--1.3.3--1.3.4.sql | 24 +++++++----- sql/pgtap.sql.in | 24 +++++++----- test/expected/faketable.out | 15 ++++++-- test/expected/viewmock.out | 3 ++ test/sql/faketable.sql | 77 ++++++++++++++++++++++++++++++++++++- test/sql/viewmock.sql | 29 ++++++++++++++ 7 files changed, 171 insertions(+), 27 deletions(-) create mode 100644 test/expected/viewmock.out create mode 100644 test/sql/viewmock.sql diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 89e5ed96f..9311beb0d 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -8588,6 +8588,7 @@ It's always well known which data are invalid for your system. Simply don't inse ### `fake_table()` ### + PERFORM fake_table( :_table_ident, :_make_table_empty, :_leave_primary_key, :_drop_not_null, :_drop_collation, :_drop_partitions ); PERFORM fake_table( :_table_ident, :_make_table_empty, :_leave_primary_key, :_drop_not_null, :_drop_collation ); PERFORM fake_table( :_table_ident, :_make_table_empty, :_leave_primary_key, :_drop_not_null ); PERFORM fake_table( :_table_ident, :_make_table_empty, :_leave_primary_key ); @@ -8611,6 +8612,10 @@ It's always well known which data are invalid for your system. Simply don't inse `:_drop_collation` : Boolean. Not yet implemented. Optional. FALSE by default. +`:_drop_partitions` +: Boolean. If TRUE any declarative partition of a table will be dropped. Works starting with PostgreSQL version 10. +Optional. FALSE by default. + Sometimes a table contains a lot of junk data, especially in a development environment. However, to ensure that your test runs on completely valid data, you clear the table before testing, insert valid data, and then run the check. This is why the _make_table_empty parameter is useful. @@ -8702,11 +8707,11 @@ But if your function has some parameters, you must specify them as follows: (_int_param int, _text_param text = null, _ts_param = now()). The simplest way to find correct signature of your function is to call get_routine_signature. It is required. -':_return_set_value' +':_return_scalar_value' : Text. Some scalar value that your mock have to return in test context. Optional. Default NULL. Obviously, you have to provide either '_return_set_value' or '_return_scalar_value'. -':_return_scalar_value' +':_return_set_value' : Text. Some SQL code forming a dataset that your mock should return in text context. You may provide a name for a prepared statement. There is a convention to name a prepared statement using the following pattern: 'mock_my_dataset'. Optional. Default NULL. @@ -8728,6 +8733,23 @@ Obviously, you have to provide either '_return_set_value' or '_return_scalar_val The simple way to find out how PostgreSQL stores the signature of your routine. Use this function to select at least the arguments with defaults ('args_with_defs') to pass this value to 'mock_func'. +### `mock_view()` ### + + PERFORM mock_func( :_view_schema, :_view_name, :_return_set_sql ); + +**Parameters** + +`:_view_schema` +: Text. This is the schema where your view is declared. Required. + +`:_view_name` +: Text. View name. Required. + +`:_return_set_sql` +: Text. Some SQL code forming a dataset that your mock should return in text context. Required. + +Creates a mock replacement for a real view. See more details about mocking above regarding the 'mock_func' function. + Assert to controls count of calls --------------------------------- diff --git a/sql/pgtap--1.3.3--1.3.4.sql b/sql/pgtap--1.3.3--1.3.4.sql index 443ea54f9..496d912a5 100644 --- a/sql/pgtap--1.3.3--1.3.4.sql +++ b/sql/pgtap--1.3.3--1.3.4.sql @@ -77,10 +77,10 @@ begin from tap_funky where "name" = _func_name; _ex_msg = format('Routine %I.%I %s does not exist.', - _func_schema, _func_name, _func_args) || E'\n' || 'Possible variants are:' || E'\n' || _variants; - raise exception '%', _ex_msg; + _func_schema, _func_name, _func_args) || E'\n' || 'Possible variants are:' || E'\n' || + coalesce(_variants, 'There is no such function in any schema'); + raise exception '%', coalesce(_ex_msg, 'Нет описания'); end; - --This is the case when we need to mock a function written in SQL. --But in order to be able to execute the mocking functionality, we need to have a function written in plpgsql. --That is why we create a hidden function which name starts with "__". @@ -89,12 +89,12 @@ begin create or replace function %1$I.__%2$I(_name text) returns %3$s language plpgsql - AS %5$sfunction%5$s + AS %4$sfunction%4$s begin - return query execute _query(%4$L); + return query execute _query(_name); end; - %5$sfunction%5$s;', - _func_schema/*1*/, _func_name/*2*/, _func_result_type/*3*/, _return_set_value/*4*/, '$'/*5*/); + %4$sfunction%4$s;', + _func_schema/*1*/, _func_name/*2*/, _func_result_type/*3*/, '$'/*4*/); execute _mock_ddl; _mock_ddl = format(' create or replace function %1$I.%2$I %3$s @@ -115,7 +115,7 @@ begin language plpgsql AS %6$sfunction%6$s begin - return query execute _query( %5$s ); + return query execute _query( ''%5$s'' ); end; %6$sfunction%6$s;', _func_schema/*1*/, _func_name/*2*/, _func_args/*3*/, _func_result_type/*4*/, @@ -143,6 +143,7 @@ create or replace function mock_view( , _view_name text , _return_set_sql text default null ) +returns void --Create a mock in place of a real view language plpgsql as $function$ @@ -155,7 +156,7 @@ declare begin _mock_ddl = format('drop view %I.%I', _view_schema, _view_name); execute _mock_ddl; - _mock_ddl = format('create view %I.%I as %s', _view_schema, _view_name, _return_set_value); + _mock_ddl = format('create view %I.%I as %s', _view_schema, _view_name, _return_set_sql); execute _mock_ddl; end; $function$; @@ -164,7 +165,7 @@ create or replace function fake_table( _make_table_empty boolean default false, _leave_primary_key boolean default false, _drop_not_null boolean DEFAULT false, - _drop_collation boolean DEFAULT false + _drop_collation boolean DEFAULT false, _drop_partitions boolean DEFAULT false ) returns void @@ -266,6 +267,9 @@ begin end if; if _drop_partitions then + if pg_version_num() < 100000 then + raise exception 'Sorry, but declarative partitioning was introduced only starting with PostgreSQL version 10.'; + end if; for _part_table in select _parts from _parts(_table.table_schema, _table.table_name) loop _fake_ddl = 'drop table if exists ' || _part_table._parts || ';'; execute _fake_ddl; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index cb7434ad4..78c02191f 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -11639,10 +11639,10 @@ begin from tap_funky where "name" = _func_name; _ex_msg = format('Routine %I.%I %s does not exist.', - _func_schema, _func_name, _func_args) || E'\n' || 'Possible variants are:' || E'\n' || _variants; - raise exception '%', _ex_msg; + _func_schema, _func_name, _func_args) || E'\n' || 'Possible variants are:' || E'\n' || + coalesce(_variants, 'There is no such function in any schema'); + raise exception '%', coalesce(_ex_msg, 'Нет описания'); end; - --This is the case when we need to mock a function written in SQL. --But in order to be able to execute the mocking functionality, we need to have a function written in plpgsql. --That is why we create a hidden function which name starts with "__". @@ -11651,12 +11651,12 @@ begin create or replace function %1$I.__%2$I(_name text) returns %3$s language plpgsql - AS %5$sfunction%5$s + AS %4$sfunction%4$s begin - return query execute _query(%4$L); + return query execute _query(_name); end; - %5$sfunction%5$s;', - _func_schema/*1*/, _func_name/*2*/, _func_result_type/*3*/, _return_set_value/*4*/, '$'/*5*/); + %4$sfunction%4$s;', + _func_schema/*1*/, _func_name/*2*/, _func_result_type/*3*/, '$'/*4*/); execute _mock_ddl; _mock_ddl = format(' create or replace function %1$I.%2$I %3$s @@ -11677,7 +11677,7 @@ begin language plpgsql AS %6$sfunction%6$s begin - return query execute _query( %5$s ); + return query execute _query( ''%5$s'' ); end; %6$sfunction%6$s;', _func_schema/*1*/, _func_name/*2*/, _func_args/*3*/, _func_result_type/*4*/, @@ -11705,6 +11705,7 @@ create or replace function mock_view( , _view_name text , _return_set_sql text default null ) +returns void --Create a mock in place of a real view language plpgsql as $function$ @@ -11717,7 +11718,7 @@ declare begin _mock_ddl = format('drop view %I.%I', _view_schema, _view_name); execute _mock_ddl; - _mock_ddl = format('create view %I.%I as %s', _view_schema, _view_name, _return_set_value); + _mock_ddl = format('create view %I.%I as %s', _view_schema, _view_name, _return_set_sql); execute _mock_ddl; end; $function$; @@ -11726,7 +11727,7 @@ create or replace function fake_table( _make_table_empty boolean default false, _leave_primary_key boolean default false, _drop_not_null boolean DEFAULT false, - _drop_collation boolean DEFAULT false + _drop_collation boolean DEFAULT false, _drop_partitions boolean DEFAULT false ) returns void @@ -11828,6 +11829,9 @@ begin end if; if _drop_partitions then + if pg_version_num() < 100000 then + raise exception 'Sorry, but declarative partitioning was introduced only starting with PostgreSQL version 10.'; + end if; for _part_table in select _parts from _parts(_table.table_schema, _table.table_name) loop _fake_ddl = 'drop table if exists ' || _part_table._parts || ';'; execute _fake_ddl; diff --git a/test/expected/faketable.out b/test/expected/faketable.out index 7a7da55c1..96dd789ab 100644 --- a/test/expected/faketable.out +++ b/test/expected/faketable.out @@ -1,5 +1,5 @@ \unset ECHO -1..14 +1..21 ok 1 - public.parent.id is primary key should pass ok 2 - public.child.id is not primary key should pass ok 3 - public.child.parent_id is not foreign key should pass @@ -11,6 +11,13 @@ ok 8 - public.child.col is null should pass ok 9 - table public.parent is empty should pass ok 10 - table public.child is empty should pass ok 11 - We can do insert into foreign key column should pass -ok 12 - public.scalar_function called once should pass -ok 13 - public.set_sql_function called twice should pass -ok 14 - public.set_sql_function(text) called once should pass +ok 12 - public.pk_plus_not_null primary key is (id, num) should pass +ok 13 - Columns tale and comments of public.pk_plus_not_null are not part of primary key should pass +ok 14 - public.pk_plus_not_null.id is not null should pass +ok 15 - public.pk_plus_not_null.num is not null should pass +ok 16 - public.pk_plus_not_null.tale is null should pass +ok 17 - public.pk_plus_not_null.comments is null should pass +ok 18 - public.parts should have no any partition should pass +ok 19 - public.scalar_function called once should pass +ok 20 - public.set_sql_function called twice should pass +ok 21 - public.set_sql_function(text) called once should pass diff --git a/test/expected/viewmock.out b/test/expected/viewmock.out new file mode 100644 index 000000000..aafd98132 --- /dev/null +++ b/test/expected/viewmock.out @@ -0,0 +1,3 @@ +\unset ECHO +1..1 +ok 1 - mock of some_view should return expected result should pass diff --git a/test/sql/faketable.sql b/test/sql/faketable.sql index cadc5c1b9..a042e61f9 100644 --- a/test/sql/faketable.sql +++ b/test/sql/faketable.sql @@ -2,7 +2,7 @@ \i test/setup.sql -- \i sql/pgtap.sql -SELECT plan(14); +SELECT plan(21); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -125,8 +125,83 @@ BEGIN END; $$ LANGUAGE plpgsql; +CREATE TABLE public.pk_plus_not_null( + id int NOT NULL, + num varchar(10) not null, + tale text not null, + comments text, + CONSTRAINT pk_plus_not_null_pk PRIMARY KEY (id, num) +); + +CREATE FUNCTION test_faking_functionality_pk_not_null() RETURNS SETOF TEXT AS $$ +BEGIN + perform fake_table( + '{public.pk_plus_not_null}'::text[], + _leave_primary_key => TRUE, + _make_table_empty => TRUE, + _drop_not_null => TRUE); + + RETURN query SELECT * FROM check_test( + col_is_pk('public', 'pk_plus_not_null', '{id, num}'::name[]), + TRUE, + 'public.pk_plus_not_null primary key is (id, num)'); + + RETURN query SELECT * FROM check_test( + col_isnt_pk('public', 'pk_plus_not_null', '{tale, comments}'::name[], 'no pk'), + TRUE, + 'Columns tale and comments of public.pk_plus_not_null are not part of primary key'); + + RETURN query SELECT * FROM check_test( + col_not_null('public', 'pk_plus_not_null', 'id', ''), + TRUE, + 'public.pk_plus_not_null.id is not null'); + + RETURN query SELECT * FROM check_test( + col_not_null('public', 'pk_plus_not_null', 'num', ''), + TRUE, + 'public.pk_plus_not_null.num is not null'); + + RETURN query SELECT * FROM check_test( + col_is_null('public', 'pk_plus_not_null', 'tale', ''), + TRUE, + 'public.pk_plus_not_null.tale is null'); + + RETURN query SELECT * FROM check_test( + col_is_null('public', 'pk_plus_not_null', 'comments', ''), + TRUE, + 'public.pk_plus_not_null.comments is null'); +END; +$$ LANGUAGE plpgsql; + +create table public.parts( + id int, + tale text, + date_key date +) +partition by range(date_key); + +create table public.part_202509 partition of public.parts + for values from ('2025-09-01') to ('2025-10-01'); + +create table public.part_202510 partition of public.parts + for values from ('2025-10-01') to ('2025-11-01'); + +create or replace FUNCTION test_faking_functionality_no_partitions() RETURNS SETOF TEXT AS $$ +BEGIN + perform fake_table( + '{public.parts}'::text[], + _drop_partitions => TRUE); + + RETURN query SELECT * FROM check_test( + partitions_are('public', 'parts', '{}'::name[], 'No partitions we expect'), + TRUE, + 'public.parts should have no any partition'); +END; +$$ LANGUAGE plpgsql; SELECT * FROM test_faking_functionality(); +SELECT * FROM test_faking_functionality_pk_not_null(); +SELECT * FROM test_faking_functionality_no_partitions(); CREATE FUNCTION test_call_count_functionality() RETURNS SETOF TEXT AS $$ BEGIN diff --git a/test/sql/viewmock.sql b/test/sql/viewmock.sql new file mode 100644 index 000000000..cf4b7fc78 --- /dev/null +++ b/test/sql/viewmock.sql @@ -0,0 +1,29 @@ +\unset ECHO +\i test/setup.sql +-- \i sql/pgtap.sql + +SELECT plan(1); + +create or replace view public.some_view +as +select * from (values(1, 'a'), (2, 'b'), (3, 'c')) as t(id, f); + +create or replace function test_view_mocking() RETURNS SETOF TEXT AS $$ +BEGIN + PREPARE some_view_should_be AS select * from (values(1, 'x'), (2, 'y'), (3, 'z')) as t(id, f) ORDER BY id; + perform mock_view('public', 'some_view', + _return_set_sql => 'select * from (values(1, ''x''), (2, ''y''), (3, ''z'')) as t(id, f)'); + PREPARE some_view_returned AS SELECT * FROM public.some_view ORDER BY id; + + RETURN query SELECT * FROM check_test( + results_eq('some_view_returned', 'some_view_should_be'), + TRUE, + 'mock of some_view should return expected result'); +END; +$$ LANGUAGE plpgsql; + +SELECT * FROM test_view_mocking(); + +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 5baf8c73ea04802f9678b4e5b477ac56a11f429b Mon Sep 17 00:00:00 2001 From: mslava Date: Sat, 25 Oct 2025 18:16:53 +0700 Subject: [PATCH 20/20] fixed a couple of typos --- sql/pgtap--1.3.3--1.3.4.sql | 4 ++-- sql/pgtap.sql.in | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sql/pgtap--1.3.3--1.3.4.sql b/sql/pgtap--1.3.3--1.3.4.sql index 496d912a5..ab196d0a5 100644 --- a/sql/pgtap--1.3.3--1.3.4.sql +++ b/sql/pgtap--1.3.3--1.3.4.sql @@ -249,11 +249,11 @@ begin and t.attnum > 0 and attnotnull and ( ( - --and the column is not part of PK when we wat ot leave PK as is + --and the column is not part of PK when we want to leave PK as is t.attname::text != all((select _keys(_table.table_schema, _table.table_name, 'p'))::text[]) and _leave_primary_key ) or - --we can drop an NOT NULL constraint as there is not PK already + --we can drop a NOT NULL constraint as there is no PK already not _leave_primary_key ); diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 78c02191f..5839a2946 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -11811,11 +11811,11 @@ begin and t.attnum > 0 and attnotnull and ( ( - --and the column is not part of PK when we wat ot leave PK as is + --and the column is not part of PK when we want to leave PK as is t.attname::text != all((select _keys(_table.table_schema, _table.table_name, 'p'))::text[]) and _leave_primary_key ) or - --we can drop an NOT NULL constraint as there is not PK already + --we can drop a NOT NULL constraint as there is no PK already not _leave_primary_key );