--- sql/pgtap.sql +++ sql/pgtap.sql @@ -5,6 +5,71 @@ -- -- http://pgtap.org/ +-- Cast booleans to text like 8.3 does. +CREATE OR REPLACE FUNCTION booltext(boolean) +RETURNS text AS 'SELECT CASE WHEN $1 then ''true'' ELSE ''false'' END;' +LANGUAGE sql IMMUTABLE STRICT; + +CREATE CAST (boolean AS text) WITH FUNCTION booltext(boolean) AS ASSIGNMENT; + +-- Cast text[]s to text like 8.3 does. +CREATE OR REPLACE FUNCTION textarray_text(text[]) +RETURNS TEXT AS 'SELECT textin(array_out($1));' +LANGUAGE sql IMMUTABLE STRICT; + +CREATE CAST (text[] AS text) WITH FUNCTION textarray_text(text[]) AS ASSIGNMENT; + +-- Cast name[]s to text like 8.3 does. +CREATE OR REPLACE FUNCTION namearray_text(name[]) +RETURNS TEXT AS 'SELECT textin(array_out($1));' +LANGUAGE sql IMMUTABLE STRICT; + +CREATE CAST (name[] AS text) WITH FUNCTION namearray_text(name[]) AS ASSIGNMENT; + +-- Compare name[]s more or less like 8.3 does. +CREATE OR REPLACE FUNCTION namearray_eq( name[], name[] ) +RETURNS bool +AS 'SELECT $1::text = $2::text;' +LANGUAGE sql IMMUTABLE STRICT; + +-- Cast text[] to regtype[] like 8.3 does. +CREATE OR REPLACE FUNCTION textarraty_regtypearray(text[]) +RETURNS REGTYPE[] AS $$ + SELECT coalesce(ARRAY( + SELECT regtypein(textout($1[i])) + FROM generate_series(1, array_upper($1, 1)) s(i) + ORDER BY i + ), '{}')::regtype[]; +$$ LANGUAGE sql IMMUTABLE STRICT; + +CREATE CAST (text[] AS regtype[]) WITH FUNCTION textarraty_regtypearray(text[]) AS ASSIGNMENT; + +CREATE OPERATOR = ( + LEFTARG = name[], + RIGHTARG = name[], + NEGATOR = <>, + PROCEDURE = namearray_eq +); + +CREATE OR REPLACE FUNCTION namearray_ne( name[], name[] ) +RETURNS bool +AS 'SELECT $1::text <> $2::text;' +LANGUAGE sql IMMUTABLE STRICT; + +CREATE OPERATOR <> ( + LEFTARG = name[], + RIGHTARG = name[], + NEGATOR = =, + PROCEDURE = namearray_ne +); + +-- Cast regtypes to text like 8.3 does. +CREATE OR REPLACE FUNCTION regtypetext(regtype) +RETURNS text AS 'SELECT textin(regtypeout($1))' +LANGUAGE sql IMMUTABLE STRICT; + +CREATE CAST (regtype AS text) WITH FUNCTION regtypetext(regtype) AS ASSIGNMENT; + CREATE OR REPLACE FUNCTION pg_version() RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -16,7 +81,12 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ - SELECT current_setting('server_version_num')::integer; + SELECT substring(s.a[1] FROM '[[:digit:]]+')::int * 10000 + + COALESCE(substring(s.a[2] FROM '[[:digit:]]+')::int, 0) * 100 + + COALESCE(substring(s.a[3] FROM '[[:digit:]]+')::int, 0) + FROM ( + SELECT string_to_array(current_setting('server_version'), '.') AS a + ) AS s; $$ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION os_name() @@ -245,21 +315,6 @@ ); $$ 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 @@ -472,9 +527,9 @@ output TEXT; BEGIN EXECUTE 'SELECT ' || - COALESCE(quote_literal( have ), 'NULL') || '::' || pg_typeof(have) || ' ' + COALESCE(quote_literal( have ), 'NULL') || '::' || pg_typeof(have)::text || ' ' || op || ' ' || - COALESCE(quote_literal( want ), 'NULL') || '::' || pg_typeof(want) + COALESCE(quote_literal( want ), 'NULL') || '::' || pg_typeof(want)::text INTO result; output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( @@ -758,7 +813,7 @@ || 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' + || regexp_replace(NULLIF( $5, ''), '^', ' ', 'g' ), ''); $$ LANGUAGE sql IMMUTABLE; @@ -2476,7 +2531,7 @@ 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.prorettype::regtype::text AS returns, p.prolang AS langoid, p.proisstrict AS is_strict, p.proisagg AS is_agg, @@ -3690,63 +3745,6 @@ 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.oid - ), - $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.oid - ), - $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( @@ -6337,17 +6335,17 @@ BEGIN -- Run the setup functions. FOR tap IN SELECT * FROM _runem(setup, false) LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + RETURN NEXT regexp_replace(tap, E'(^|\n)', E'\\1 ', 'g'); END LOOP; -- Run the actual test function. FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + RETURN NEXT regexp_replace(tap, E'(^|\n)', E'\\1 ', 'g'); END LOOP; -- Run the teardown functions. FOR tap IN SELECT * FROM _runem(teardown, false) LOOP - RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + RETURN NEXT regexp_replace(tap, E'(^|\n)', E'\\1 ', 'g'); END LOOP; -- Emit the plan. @@ -6386,7 +6384,7 @@ tok := FALSE; RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname - )), '^', ' ', 'gn'); + )), '^', ' ', 'g'); errmsg := NULL; END IF; END; @@ -6499,13 +6497,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP - extras := extras || rec::text; + extras := array_append(extras, textin(record_out(rec))); END LOOP; -- Find missing records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' EXCEPT ' || $4 || 'SELECT * FROM ' || have LOOP - missing := missing || rec::text; + missing := array_append(missing, textin(record_out(rec))); END LOOP; -- Drop the temporary tables. @@ -6729,7 +6727,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP - results := results || rec::text; + results := array_append(results, textin(record_out(rec))); END LOOP; -- Drop the temporary tables. @@ -6824,11 +6822,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP - IF have_rec::text IS DISTINCT FROM want_rec::text OR have_found <> want_found THEN + IF textin(record_out(have_rec)) IS DISTINCT FROM textin(record_out(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 + ' have: ' || CASE WHEN have_found THEN textin(record_out(have_rec)) ELSE 'NULL' END || E'\n' || + ' want: ' || CASE WHEN want_found THEN textin(record_out(want_rec)) ELSE 'NULL' END ); END IF; rownum = rownum + 1; @@ -6843,9 +6841,9 @@ WHEN datatype_mismatch THEN 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 + CASE WHEN textin(record_out(have_rec)) = textin(record_out(want_rec)) THEN '' ELSE E':\n' || + ' have: ' || CASE WHEN have_found THEN textin(record_out(have_rec)) ELSE 'NULL' END || E'\n' || + ' want: ' || CASE WHEN want_found THEN textin(record_out(want_rec)) ELSE 'NULL' END END ); END; @@ -6981,7 +6979,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP - IF have_rec::text IS DISTINCT FROM want_rec::text OR have_found <> want_found THEN + IF textin(record_out(have_rec)) IS DISTINCT FROM textin(record_out(want_rec)) OR have_found <> want_found THEN RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; @@ -6995,8 +6993,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\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 + ' have: ' || CASE WHEN have_found THEN textin(record_out(have_rec)) ELSE 'NULL' END || E'\n' || + ' want: ' || CASE WHEN want_found THEN textin(record_out(want_rec)) ELSE 'NULL' END ); END; $$ LANGUAGE plpgsql; @@ -7121,9 +7119,9 @@ 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 || '"'); + IF typeof = $2 THEN RETURN ok(true, $3 || ' isa ' || $2::text ); END IF; + RETURN ok(false, $3 || ' isa ' || $2::text ) || E'\n' || + diag(' ' || $3 || ' isn''t a "' || $2::text || '" it''s a "' || typeof::text || '"'); END; $$ LANGUAGE plpgsql; @@ -7144,7 +7142,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP - extras := extras || rec::text; + extras := extras || textin(record_out(rec)); END LOOP; -- What extra records do we have? @@ -7312,7 +7310,7 @@ 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 NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem) AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT @@ -7330,7 +7328,7 @@ 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 NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem) AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) ), @@ -7363,7 +7361,7 @@ 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 NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem) 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']) ) @@ -7382,7 +7380,7 @@ 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 NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem) 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']) ) @@ -7666,10 +7664,12 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; - IF NOT rec::text IS DISTINCT FROM $2::text THEN RETURN ok(true, $3); END IF; + IF NOT textin(record_out(rec)) IS DISTINCT FROM textin(record_out($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 + ' have: ' || CASE WHEN rec IS NULL THEN 'NULL' ELSE textin(record_out(rec)) END || + E'\n want: ' || CASE WHEN $2 IS NULL THEN 'NULL' ELSE textin(record_out($2)) END ); END; $$ LANGUAGE plpgsql; @@ -7816,7 +7816,7 @@ CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) RETURNS TEXT AS $$ - SELECT $1 || substring($2::regoperator::text, '[(][^)]+[)]$') + SELECT $1 || substring(textin(regoperatorout($2::regoperator)), '[(][^)]+[)]$') $$ LANGUAGE SQL; -- operators_are( schema, operators[], description ) @@ -7825,7 +7825,7 @@ SELECT _areni( 'operators', ARRAY( - SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype + SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype::text FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 @@ -7837,7 +7837,7 @@ 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 + SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype::text FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 @@ -7858,7 +7858,7 @@ SELECT _areni( 'operators', ARRAY( - SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype + SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype::text 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) @@ -7871,7 +7871,7 @@ 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 + SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype::text 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) @@ -8584,40 +8584,6 @@ ); $$ 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)